mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-20 11:21:49 +00:00
feat: add the MariaDB RDS image preset
Adds scripts/images/rds-mariadb/: the manifest with every mariadb package pinned =~11.8, setup.sh, the rds-datadir and rds-agent services, and the static include drop-in that points MariaDB at the generated configuration on the data volume. The AMI carries engine=mariadb, engine-version=11.8 and the data-volume contract tag, so resolveEngineAMI can select it. The include is named zz- rather than 00-: MariaDB walks an include directory in byte order and takes the last occurrence of a setting, so a digit prefix would sort ahead of Alpine's packaged mariadb-server.cnf and let a distribution default beat a platform-owned one. setup.sh asserts the resulting order against the real directory at build time. The include target is baked empty as well, because MariaDB treats an includedir it cannot open as a fatal defaults-parsing error and a boot with no data volume would otherwise lose the client alongside the server. The packaged drop-in also sets skip-networking, which would leave the endpoint resolving and the health probe passing while nothing on the customer ENI could connect. setup.sh disables it at the source and asserts no configuration file still does. The mariadb service is deliberately not enabled: the packaged service only warns about an empty datadir rather than refusing, so an engine enabled before the bootstrap exists would launch mysqld_safe against an unbootstrapped volume. rds-datadir moves to scripts/images/common/ rather than being copied. Volume selection, format authorization and mount are already engine-neutral; the mount point and owning user are the only per-engine facts and now come from each preset's own service. The script fails closed when either is unset, so no image can inherit another engine's layout by omission.
This commit is contained in:
@@ -1,10 +1,14 @@
|
||||
#!/bin/sh
|
||||
# rds-datadir — select, authorize, prepare, and mount the exact data volume
|
||||
# assigned by the control plane before rds-init touches the PostgreSQL datadir.
|
||||
# assigned by the control plane before rds-init touches the engine's datadir.
|
||||
#
|
||||
# Shared by every RDS image preset. The mount point and the OS user that owns it
|
||||
# are the one engine-specific pair here, and both are supplied by the preset's
|
||||
# own service rather than defaulted, so no image can inherit another's layout.
|
||||
set -eu
|
||||
|
||||
DATA_MOUNT="${RDS_DATA_MOUNT:-/var/lib/postgresql}"
|
||||
PG_USER="${RDS_PG_USER:-postgres}"
|
||||
DATA_MOUNT="${RDS_DATA_MOUNT:-}"
|
||||
ENGINE_USER="${RDS_ENGINE_USER:-}"
|
||||
SYS_BLOCK="${RDS_SYS_BLOCK:-/sys/block}"
|
||||
DEV_DIR="${RDS_DEV_DIR:-/dev}"
|
||||
MOUNTS_FILE="${RDS_MOUNTS_FILE:-/proc/mounts}"
|
||||
@@ -103,6 +107,14 @@ consume_format_authorization() {
|
||||
}
|
||||
|
||||
main() {
|
||||
# An unset layout is a broken image, not something to guess at: mounting the
|
||||
# customer's volume where the engine is not looking would read as an empty
|
||||
# datadir and bootstrap a second, empty database beside their data.
|
||||
[ -n "${DATA_MOUNT}" ] ||
|
||||
fail "RDS_DATA_MOUNT is not set; refusing to guess where this engine's datadir belongs"
|
||||
[ -n "${ENGINE_USER}" ] ||
|
||||
fail "RDS_ENGINE_USER is not set; refusing to guess which user owns this engine's datadir"
|
||||
|
||||
load_handoff
|
||||
|
||||
command -v blkid >/dev/null 2>&1 ||
|
||||
@@ -184,8 +196,8 @@ main() {
|
||||
|
||||
mount -t "${_fstype}" "${_dev}" "${DATA_MOUNT}" ||
|
||||
fail "could not mount ${_dev} at ${DATA_MOUNT}"
|
||||
chown "${PG_USER}:${PG_USER}" "${DATA_MOUNT}" ||
|
||||
fail "could not set ${PG_USER} ownership on ${DATA_MOUNT}"
|
||||
chown "${ENGINE_USER}:${ENGINE_USER}" "${DATA_MOUNT}" ||
|
||||
fail "could not set ${ENGINE_USER} ownership on ${DATA_MOUNT}"
|
||||
chmod 0750 "${DATA_MOUNT}"
|
||||
|
||||
log "${DATA_MOUNT} ready on ${_dev}"
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
# rds-datadir is shared by every engine preset and refuses to guess a layout, so
|
||||
# each preset's service is the only statement of where its volume mounts and who
|
||||
# owns it. A preset that stopped exporting them would fail closed at boot; a
|
||||
# preset that exported another engine's would mount the customer's volume where
|
||||
# its engine is not looking, which reads as an empty datadir.
|
||||
|
||||
SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
|
||||
IMAGES=$(CDPATH='' cd -- "${SCRIPT_DIR}/.." && pwd)
|
||||
WORK=$(mktemp -d)
|
||||
trap 'rm -rf "${WORK}"' EXIT
|
||||
|
||||
FAILS=0
|
||||
fail() { echo "FAIL: $*" >&2; FAILS=$((FAILS + 1)); }
|
||||
pass() { echo "ok: $*"; }
|
||||
|
||||
# Sourced rather than executed: start_pre is what OpenRC runs, and the values it
|
||||
# exports are what reach the mount script.
|
||||
layout() {
|
||||
(
|
||||
# shellcheck disable=SC1090
|
||||
. "${IMAGES}/$1/rds-datadir.initd"
|
||||
# Read by the sourced start_pre, which shellcheck cannot see.
|
||||
export AGENT_ENV="$2"
|
||||
start_pre
|
||||
printf '%s %s\n' "${RDS_DATA_MOUNT:-}" "${RDS_ENGINE_USER:-}"
|
||||
)
|
||||
}
|
||||
|
||||
check_preset() {
|
||||
_preset=$1
|
||||
_mount=$2
|
||||
_user=$3
|
||||
|
||||
_got=$(layout "${_preset}" "${WORK}/absent.env")
|
||||
if [ "${_got}" = "${_mount} ${_user}" ]; then
|
||||
pass "${_preset}: exports ${_mount} owned by ${_user}"
|
||||
else
|
||||
fail "${_preset}: exports '${_got}', want '${_mount} ${_user}'"
|
||||
fi
|
||||
|
||||
# The control plane can move the mount point without a rebuild, so a
|
||||
# delivered value has to win over the image's own default.
|
||||
printf "RDS_DATA_MOUNT=%s\n" "${WORK}/delivered" > "${WORK}/agent.env"
|
||||
_got=$(layout "${_preset}" "${WORK}/agent.env")
|
||||
if [ "${_got}" = "${WORK}/delivered ${_user}" ]; then
|
||||
pass "${_preset}: a delivered mount point wins"
|
||||
else
|
||||
fail "${_preset}: delivered mount point ignored, got '${_got}'"
|
||||
fi
|
||||
}
|
||||
|
||||
check_preset rds-postgres /var/lib/postgresql postgres
|
||||
check_preset rds-mariadb /var/lib/mysql mysql
|
||||
|
||||
if [ "${FAILS}" -eq 0 ]; then
|
||||
echo "rds-datadir.initd: all presets state their layout"
|
||||
exit 0
|
||||
fi
|
||||
echo "FAILED: ${FAILS} case(s)"
|
||||
exit 1
|
||||
+25
@@ -56,6 +56,7 @@ fail() { echo "FAIL: $*"; FAILS=$((FAILS + 1)); }
|
||||
pass() { echo "ok: $*"; }
|
||||
|
||||
DATA_MOUNT="${WORK}/data"
|
||||
ENGINE_USER="dbengine"
|
||||
SYS_BLOCK="${WORK}/sys/block"
|
||||
DEV_DIR="${WORK}/dev"
|
||||
MOUNTS="${WORK}/mounts"
|
||||
@@ -91,6 +92,8 @@ EOF
|
||||
}
|
||||
|
||||
reset_state() {
|
||||
DATA_MOUNT="${WORK}/data"
|
||||
ENGINE_USER="dbengine"
|
||||
rm -rf "${WORK}/sys" "${DEV_DIR}" "${DATA_MOUNT}" "$(dirname "${HANDOFF}")"
|
||||
mkdir -p "${SYS_BLOCK}" "${DEV_DIR}" "${DATA_MOUNT}"
|
||||
printf '/dev/vda1 / ext4 rw,relatime 0 0\n' > "${MOUNTS}"
|
||||
@@ -104,6 +107,7 @@ reset_state() {
|
||||
|
||||
run() {
|
||||
env RDS_DATA_MOUNT="${DATA_MOUNT}" \
|
||||
RDS_ENGINE_USER="${ENGINE_USER}" \
|
||||
RDS_SYS_BLOCK="${SYS_BLOCK}" \
|
||||
RDS_DEV_DIR="${DEV_DIR}" \
|
||||
RDS_MOUNTS_FILE="${MOUNTS}" \
|
||||
@@ -152,6 +156,9 @@ if run_ok "authorized blank exact volume"; then
|
||||
grep -q "mkfs.ext4 .*${DEV_DIR}/vdc" "${MKFS_CALLS}" && pass "authorized: exact disk formatted" || fail "authorized: exact disk not formatted"
|
||||
grep -q -- '-m 0' "${MKFS_CALLS}" && pass "authorized: no reserved blocks" || fail "authorized: reserved blocks left on"
|
||||
grep -q "mount -t ext4 ${DEV_DIR}/vdc ${DATA_MOUNT}" "${MOUNT_CALLS}" && pass "authorized: mounted" || fail "authorized: not mounted"
|
||||
# The engine reads its own datadir, so the mount is handed to the preset's
|
||||
# user rather than to whichever engine this script was first written for.
|
||||
grep -q "chown ${ENGINE_USER}:${ENGINE_USER} ${DATA_MOUNT}" "${CHOWN_CALLS}" && pass "authorized: handed to the engine user" || fail "authorized: not handed to ${ENGINE_USER}"
|
||||
grep -q '^RDS_MASTER_PASSWORD=' "${HANDOFF}" && pass "authorized: bootstrap material preserved" || fail "authorized: bootstrap material removed"
|
||||
if grep -q '^RDS_FORMAT_AUTHORIZED=' "${HANDOFF}"; then fail "authorized: grant retained"; else pass "authorized: grant consumed"; fi
|
||||
fi
|
||||
@@ -309,6 +316,24 @@ printf '%s %s ext4 rw 0 0\n' "${DEV_DIR}/wrong" "${DATA_MOUNT}" >> "${MOUNTS}"
|
||||
run_fails "already mounted wrong device" "not expected device"
|
||||
nothing_formatted "already mounted wrong device"
|
||||
|
||||
# An image whose service states no layout must not fall back to another engine's
|
||||
# paths: a volume mounted where the engine is not looking reads as an empty
|
||||
# datadir, and the bootstrap that follows serves an empty database beside the
|
||||
# customer's data. Both refusals come before the handoff is even read.
|
||||
reset_state
|
||||
add_disk vdb volexpected
|
||||
write_handoff vol-expected volexpected 1 true
|
||||
DATA_MOUNT=""
|
||||
run_fails "no mount point" "RDS_DATA_MOUNT is not set"
|
||||
nothing_formatted "no mount point"
|
||||
|
||||
reset_state
|
||||
add_disk vdb volexpected
|
||||
write_handoff vol-expected volexpected 1 true
|
||||
ENGINE_USER=""
|
||||
run_fails "no engine user" "RDS_ENGINE_USER is not set"
|
||||
nothing_formatted "no engine user"
|
||||
|
||||
if [ "${FAILS}" -eq 0 ]; then
|
||||
echo "PASS: all rds-datadir cases"
|
||||
exit 0
|
||||
@@ -0,0 +1,55 @@
|
||||
IMAGE_NAME="rds-mariadb"
|
||||
IMAGE_DESCRIPTION="Spinifex RDS MariaDB instance — Alpine + MariaDB 11.8"
|
||||
DISTRO="alpine"
|
||||
ALPINE_VERSION="3.24.1"
|
||||
|
||||
# Engine binaries and per-boot diagnostics only. The datadir lives on the data
|
||||
# volume, so the boot volume stays a disposable COW clone.
|
||||
IMAGE_SIZE="2G"
|
||||
|
||||
# Alpine's mariadb package name carries no version, unlike postgresql18, so the
|
||||
# fuzzy pin is what keeps a rebuild on this series: =~11.8 admits a patch release
|
||||
# and refuses a series bump, which would otherwise produce an image tagged
|
||||
# engine-version=11.8 running 11.9. mariadb-common carries my.cnf and the charset
|
||||
# and error-message data; the client package carries the binaries the agent
|
||||
# probes and rotates passwords with.
|
||||
# blkid is a hard requirement — without it rds-datadir refuses to run rather
|
||||
# than risk formatting a customer's datadir.
|
||||
APK_PACKAGES="mariadb=~11.8 mariadb-client=~11.8 mariadb-common=~11.8 mariadb-openrc=~11.8 ca-certificates cloud-utils-growpart e2fsprogs-extra xfsprogs blkid openrc"
|
||||
|
||||
# rds-agent is the only built binary (static, FIPS boot like the host build).
|
||||
BUILD_COMMANDS="CGO_ENABLED=0 GOFIPS140=v1.0.0 go build -ldflags \"-s -w\" -o bin/rds-agent ./cmd/rds-agent"
|
||||
|
||||
INSTALL_BINARIES="bin/rds-agent:/usr/local/bin/rds-agent"
|
||||
|
||||
# mulga-mgmt-net is shared with eks-node: this is a three-NIC system VM (system
|
||||
# ENI, customer ENI, mgmt0), so the host's fw_cfg netcfg blob needs a consumer in
|
||||
# the guest, or mgmt0 never gets its static address and rds-agent cannot reach
|
||||
# the gateway, and the customer ENI never comes up so nothing reaches the DB.
|
||||
INSTALL_FILES="\
|
||||
scripts/images/common/rds-datadir:/usr/local/sbin/rds-datadir \
|
||||
scripts/images/rds-mariadb/rds-datadir.initd:/etc/init.d/rds-datadir \
|
||||
scripts/images/rds-mariadb/rds-agent.initd:/etc/init.d/rds-agent \
|
||||
scripts/images/common/mulga-mgmt-net.initd:/etc/init.d/mulga-mgmt-net \
|
||||
scripts/images/common/mulga-mgmt-net-routes.initd:/etc/init.d/mulga-mgmt-net-routes \
|
||||
scripts/images/common/mulga-mgmt-net.sh:/usr/local/sbin/mulga-mgmt-net \
|
||||
scripts/images/rds-mariadb/zz-rds-include.cnf:/etc/my.cnf.d/zz-rds-include.cnf"
|
||||
|
||||
# Each service's own depend() asserts the ordering; this lists it to match.
|
||||
# mulga-mgmt-net is NOT here: setup.sh adds it to the boot runlevel, since a
|
||||
# default-runlevel entry would run after cloud-init-local and be too late. Its
|
||||
# route-enforcement second pass is the opposite — it has to follow cloud-init, so
|
||||
# the default runlevel is exactly where it belongs.
|
||||
#
|
||||
# mariadb is deliberately absent while this image has no bootstrap: the packaged
|
||||
# service only warns about an empty datadir instead of refusing, so an enabled
|
||||
# engine would launch mysqld_safe against an unbootstrapped data volume.
|
||||
ENABLE_SERVICES="rds-datadir rds-agent mulga-mgmt-net-routes"
|
||||
|
||||
SETUP_SCRIPT="scripts/images/rds-mariadb/setup.sh"
|
||||
|
||||
AMI_NAME="spinifex-rds-mariadb"
|
||||
|
||||
# The contract tag prevents the control plane from issuing a format grant to an
|
||||
# older image that still discovers generic vol* devices.
|
||||
SYSTEM_TAG="spinifex:managed-by=rds engine=mariadb engine-version=11.8 rds-data-volume-contract=format-auth-v1"
|
||||
@@ -0,0 +1,77 @@
|
||||
#!/sbin/openrc-run
|
||||
# rds-agent — Spinifex RDS in-guest agent. Reaches the control plane over
|
||||
# TLS+SigV4 (never NATS), signing with IMDS instance-role credentials. Static
|
||||
# config comes from /etc/spinifex-rds/agent.env, which carries no secrets.
|
||||
|
||||
description="Spinifex RDS instance agent"
|
||||
command="/usr/local/bin/rds-agent"
|
||||
command_background="yes"
|
||||
pidfile="/run/rds-agent.pid"
|
||||
output_log="/var/log/rds-agent.log"
|
||||
error_log="/var/log/rds-agent.log"
|
||||
|
||||
start_stop_daemon_args="--wait 2000"
|
||||
|
||||
AGENT_ENV="/etc/spinifex-rds/agent.env"
|
||||
|
||||
# The agent retries indefinitely, so this only bounds how long the boot waits
|
||||
# before letting rds-init fail visibly rather than hanging forever.
|
||||
handoff_timeout="${RDS_HANDOFF_TIMEOUT:-120}"
|
||||
|
||||
# How much of the agent log to mirror to the console when the handoff times out.
|
||||
# Enough to carry a few backed-off retries with their errors, not the whole boot.
|
||||
handoff_tail="${RDS_HANDOFF_TAIL:-20}"
|
||||
|
||||
depend() {
|
||||
need net
|
||||
after localmount
|
||||
before rds-datadir rds-init mariadb
|
||||
}
|
||||
|
||||
start_pre() {
|
||||
# An absent file is tolerated, so the agent still starts and logs why it
|
||||
# cannot reach the gateway.
|
||||
if [ -f "${AGENT_ENV}" ]; then
|
||||
# shellcheck disable=SC1090
|
||||
. "${AGENT_ENV}"
|
||||
export RDS_GATEWAY_URL RDS_GATEWAY_CA RDS_REGION RDS_DB_INSTANCE_IDENTIFIER \
|
||||
RDS_ENGINE RDS_ENGINE_VERSION RDS_HANDOFF_DIR RDS_ENGINE_HOST RDS_ENGINE_PORT \
|
||||
RDS_POLL_WAIT RDS_DATA_MOUNT
|
||||
fi
|
||||
}
|
||||
|
||||
start_post() {
|
||||
# Resolve this after start_pre loads agent.env so custom handoff directories
|
||||
# are shared by the agent, this wait, and rds-init.
|
||||
HANDOFF_ENV="${RDS_HANDOFF_DIR:-/run/spinifex-rds}/bootstrap.env"
|
||||
|
||||
# `before rds-init` only orders the starts; rds-init needs the fetched config
|
||||
# itself. The agent writes bootstrap.env last and renames it into place, so
|
||||
# its appearance means the whole handoff is there.
|
||||
ebegin "Waiting for the RDS bootstrap handoff"
|
||||
_waited=0
|
||||
while [ ! -f "${HANDOFF_ENV}" ]; do
|
||||
if [ "${_waited}" -ge "${handoff_timeout}" ]; then
|
||||
eend 1 "no bootstrap handoff after ${handoff_timeout}s"
|
||||
# Mirror the agent's own tail onto the console. The log lives inside a
|
||||
# guest nothing can reach when this fails — an unreachable gateway is
|
||||
# the usual cause — so the host-side console capture is the only way
|
||||
# the dial error survives, and without it the failure reads as silence.
|
||||
if [ -s "${output_log}" ]; then
|
||||
einfo "last ${handoff_tail} lines of ${output_log}:"
|
||||
tail -n "${handoff_tail}" "${output_log}" 2>/dev/null | while IFS= read -r _line; do
|
||||
einfo " [rds-agent] ${_line}"
|
||||
done
|
||||
else
|
||||
einfo "${output_log} is empty — the agent produced no output at all"
|
||||
fi
|
||||
# The agent keeps retrying, so it is left running: rds-init fails on
|
||||
# the missing handoff and the engine stays down, which is what the
|
||||
# control plane observes.
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
_waited=$((_waited + 1))
|
||||
done
|
||||
eend 0
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
|
||||
CASE=$(mktemp -d)
|
||||
trap 'rm -rf "${CASE}"' EXIT
|
||||
|
||||
source_time_handoff="${CASE}/source-time"
|
||||
configured_handoff="${CASE}/configured"
|
||||
mkdir -p "${configured_handoff}"
|
||||
touch "${configured_handoff}/bootstrap.env"
|
||||
{
|
||||
printf 'RDS_HANDOFF_DIR=%s\n' "${configured_handoff}"
|
||||
printf 'RDS_ENGINE=mariadb\n'
|
||||
} >"${CASE}/agent.env"
|
||||
|
||||
# Simulate OpenRC sourcing the service before start_pre loads agent.env.
|
||||
export RDS_HANDOFF_DIR="${source_time_handoff}"
|
||||
# shellcheck source=rds-agent.initd
|
||||
. "${SCRIPT_DIR}/rds-agent.initd"
|
||||
export AGENT_ENV="${CASE}/agent.env"
|
||||
export handoff_timeout=0
|
||||
|
||||
EEND_STATUS=
|
||||
ebegin() { :; }
|
||||
einfo() { :; }
|
||||
eend() { EEND_STATUS=$1; }
|
||||
|
||||
start_pre
|
||||
start_post
|
||||
|
||||
if [ "${HANDOFF_ENV}" != "${configured_handoff}/bootstrap.env" ]; then
|
||||
echo "FAIL: handoff path did not use RDS_HANDOFF_DIR from agent.env" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ "${EEND_STATUS}" != "0" ]; then
|
||||
echo "FAIL: post-start wait did not find the configured handoff" >&2
|
||||
exit 1
|
||||
fi
|
||||
# The agent checks this against the engine its own image bakes. Left out of the
|
||||
# export list it would read as no assertion at all, so a VM launched as the
|
||||
# wrong engine would bootstrap instead of refusing.
|
||||
if [ "${RDS_ENGINE:-}" != "mariadb" ]; then
|
||||
echo "FAIL: start_pre did not export RDS_ENGINE from agent.env" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "rds-agent.initd: all tests passed"
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/sbin/openrc-run
|
||||
# rds-datadir — boot oneshot that discovers the hot-plugged data volume, formats
|
||||
# it if authorized, and mounts it at the datadir mount point. It runs after the
|
||||
# agent has written the authenticated handoff and before anything can use the
|
||||
# MariaDB datadir. Soft ordering keeps a failed boot visible on the console.
|
||||
|
||||
description="Spinifex RDS data volume mount"
|
||||
|
||||
AGENT_ENV="/etc/spinifex-rds/agent.env"
|
||||
|
||||
depend() {
|
||||
after localmount rds-agent
|
||||
before rds-init mariadb
|
||||
}
|
||||
|
||||
start_pre() {
|
||||
# This image's layout, stated here because the mount script is shared by
|
||||
# every engine preset. A delivered RDS_DATA_MOUNT still wins, so the control
|
||||
# plane can move the mount point without rebuilding the image.
|
||||
RDS_DATA_MOUNT="/var/lib/mysql"
|
||||
RDS_ENGINE_USER="mysql"
|
||||
if [ -f "${AGENT_ENV}" ]; then
|
||||
# shellcheck disable=SC1090
|
||||
. "${AGENT_ENV}"
|
||||
fi
|
||||
export RDS_HANDOFF_DIR RDS_DATA_MOUNT RDS_ENGINE_USER
|
||||
}
|
||||
|
||||
start() {
|
||||
ebegin "Mounting the RDS data volume"
|
||||
# To the console for the same reason rds-init's is: the serial log is the
|
||||
# only view of a boot that failed before the engine came up.
|
||||
/usr/local/sbin/rds-datadir > /dev/console 2>&1
|
||||
eend $?
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
# The engine this preset bakes, and the series it pins, are stated in places that
|
||||
# have to agree: the stamp setup.sh writes, the manifest tags resolveEngineAMI
|
||||
# selects on, the apk pin the packages are installed from, and the engine value
|
||||
# the control plane launches instances with. A drift between them launches the
|
||||
# wrong image, refuses the right one, or reports a version the VM is not running.
|
||||
#
|
||||
# This preset carries no bootstrap yet, so the datadir stamp and the agent's
|
||||
# layout table are checked by the postgres preset's copy of this test only.
|
||||
|
||||
SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
|
||||
REPO_ROOT=$(CDPATH='' cd -- "${SCRIPT_DIR}/../../.." && pwd)
|
||||
ENGINE="mariadb"
|
||||
ENGINE_GO="${REPO_ROOT}/spinifex/handlers/rds/engine_mariadb.go"
|
||||
|
||||
FAILS=0
|
||||
fail() { echo "FAIL: $*" >&2; FAILS=$((FAILS + 1)); }
|
||||
|
||||
stamped=$(sed -n "s|^printf '\\(.*\\)\\\\n' > /etc/spinifex-rds/engine\$|\\1|p" "${SCRIPT_DIR}/setup.sh")
|
||||
if [ "${stamped}" != "${ENGINE}" ]; then
|
||||
fail "setup.sh stamps engine '${stamped}', want '${ENGINE}'"
|
||||
fi
|
||||
|
||||
if ! grep -q "engine=${ENGINE} " "${SCRIPT_DIR}/manifest.conf"; then
|
||||
fail "manifest.conf SYSTEM_TAG does not carry engine=${ENGINE}"
|
||||
grep '^SYSTEM_TAG' "${SCRIPT_DIR}/manifest.conf" >&2
|
||||
fi
|
||||
|
||||
if ! grep -q "Name: *\"${ENGINE}\"" "${ENGINE_GO}"; then
|
||||
fail "the control plane has no engine named ${ENGINE}"
|
||||
fi
|
||||
|
||||
# The tag is what an instance's recorded EngineVersion resolves an AMI by, so it
|
||||
# has to be the same series the control plane pins and the packages install.
|
||||
version=$(sed -n 's/^[[:space:]]*MajorVersion: *"\([^"]*\)".*/\1/p' "${ENGINE_GO}")
|
||||
if [ -z "${version}" ]; then
|
||||
fail "could not read MajorVersion from ${ENGINE_GO}"
|
||||
else
|
||||
if ! grep -q "engine-version=${version} " "${SCRIPT_DIR}/manifest.conf"; then
|
||||
fail "manifest.conf SYSTEM_TAG does not carry engine-version=${version}"
|
||||
grep '^SYSTEM_TAG' "${SCRIPT_DIR}/manifest.conf" >&2
|
||||
fi
|
||||
# Alpine's package name carries no version, so this fuzzy pin is the only
|
||||
# thing standing between a rebuild and a silent series bump.
|
||||
if ! grep -q "APK_PACKAGES=.*${ENGINE}=~${version} " "${SCRIPT_DIR}/manifest.conf"; then
|
||||
fail "manifest.conf does not pin the ${ENGINE} packages to =~${version}"
|
||||
grep '^APK_PACKAGES' "${SCRIPT_DIR}/manifest.conf" >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${FAILS}" -eq 0 ]; then
|
||||
echo "rds-engine-stamp: all tests passed"
|
||||
exit 0
|
||||
fi
|
||||
exit 1
|
||||
Executable
+74
@@ -0,0 +1,74 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
# The generated parameter files live on the data volume and reach the server
|
||||
# through one baked include. Three properties make that work, and each fails
|
||||
# silently rather than loudly if it breaks: the include must be read after every
|
||||
# packaged drop-in, it must point at the mount rds-datadir uses, and it must be
|
||||
# named so MariaDB reads it at all.
|
||||
|
||||
SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
|
||||
INCLUDE="zz-rds-include.cnf"
|
||||
|
||||
FAILS=0
|
||||
fail() { echo "FAIL: $*" >&2; FAILS=$((FAILS + 1)); }
|
||||
pass() { echo "ok: $*"; }
|
||||
|
||||
# MariaDB reads only .cnf and .ini out of an include directory. Any other suffix
|
||||
# would leave the customer's parameters silently unread.
|
||||
case "${INCLUDE}" in
|
||||
*.cnf) pass "the include is named so MariaDB reads it" ;;
|
||||
*) fail "${INCLUDE} is not a name MariaDB reads out of an include directory" ;;
|
||||
esac
|
||||
|
||||
if [ ! -f "${SCRIPT_DIR}/${INCLUDE}" ]; then
|
||||
fail "${INCLUDE} is missing from the preset"
|
||||
fi
|
||||
if ! grep -q "${INCLUDE}:/etc/my.cnf.d/${INCLUDE}" "${SCRIPT_DIR}/manifest.conf"; then
|
||||
fail "manifest.conf does not install ${INCLUDE} into /etc/my.cnf.d"
|
||||
fi
|
||||
|
||||
# Alpine's own drop-in is the file this has to outsort: MariaDB walks the include
|
||||
# directory in byte order and takes the last occurrence of a setting, so a digit
|
||||
# prefix would sort ahead of it and let a distribution default beat the platform.
|
||||
packaged="mariadb-server.cnf"
|
||||
if [ "$(printf '%s\n%s\n' "${INCLUDE}" "${packaged}" | LC_ALL=C sort | tail -n 1)" = "${INCLUDE}" ]; then
|
||||
pass "the include is read after ${packaged}"
|
||||
else
|
||||
fail "${packaged} is read after ${INCLUDE}, so packaged defaults win"
|
||||
fi
|
||||
|
||||
# setup.sh asserts the same property against the real directory at build time.
|
||||
grep -q 'LC_ALL=C sort | tail -n 1' "${SCRIPT_DIR}/setup.sh" ||
|
||||
fail "setup.sh does not assert the include is the last drop-in"
|
||||
|
||||
include_dir=$(sed -n 's/^!includedir[[:space:]]*//p' "${SCRIPT_DIR}/${INCLUDE}")
|
||||
if [ -z "${include_dir}" ]; then
|
||||
fail "${INCLUDE} declares no !includedir"
|
||||
else
|
||||
# The generated files survive a VM replacement only by living on the data
|
||||
# volume, so the include has to sit under the mount rds-datadir provides.
|
||||
data_mount=$(sed -n 's/^[[:space:]]*RDS_DATA_MOUNT="\([^"]*\)".*/\1/p' "${SCRIPT_DIR}/rds-datadir.initd")
|
||||
case "${include_dir}" in
|
||||
"${data_mount}"/*) pass "the include target is on the data volume at ${include_dir}" ;;
|
||||
*) fail "${include_dir} is not under the data mount ${data_mount:-<unset>}" ;;
|
||||
esac
|
||||
|
||||
# An !includedir MariaDB cannot open is a fatal defaults-parsing error, which
|
||||
# would take the client down alongside the server on a boot with no volume.
|
||||
grep -q "install -d .* ${include_dir}\$" "${SCRIPT_DIR}/setup.sh" ||
|
||||
fail "setup.sh does not bake ${include_dir} into the image"
|
||||
fi
|
||||
|
||||
# The packaged drop-in disables TCP. Left in place, the endpoint resolves and the
|
||||
# health probe passes while nothing on the customer ENI can ever connect.
|
||||
grep -q 's/\^skip-networking\$/' "${SCRIPT_DIR}/setup.sh" ||
|
||||
fail "setup.sh does not disable the packaged skip-networking"
|
||||
grep -q "skip\[-_\]networking" "${SCRIPT_DIR}/setup.sh" ||
|
||||
fail "setup.sh does not assert that no configuration file disables TCP"
|
||||
|
||||
if [ "${FAILS}" -eq 0 ]; then
|
||||
echo "rds-mycnf: all tests passed"
|
||||
exit 0
|
||||
fi
|
||||
exit 1
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
|
||||
WORK=$(mktemp -d)
|
||||
trap 'rm -rf "${WORK}"' EXIT
|
||||
|
||||
capture_dependencies() {
|
||||
_script=$1
|
||||
(
|
||||
need() { echo "need $*"; }
|
||||
after() { echo "after $*"; }
|
||||
before() { echo "before $*"; }
|
||||
# shellcheck disable=SC1090
|
||||
. "${SCRIPT_DIR}/${_script}"
|
||||
depend
|
||||
)
|
||||
}
|
||||
|
||||
assert_line() {
|
||||
_file=$1
|
||||
_line=$2
|
||||
grep -Fxq "${_line}" "${_file}" || {
|
||||
echo "FAIL: ${_file} missing dependency: ${_line}" >&2
|
||||
cat "${_file}" >&2
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
capture_dependencies rds-agent.initd > "${WORK}/agent"
|
||||
capture_dependencies rds-datadir.initd > "${WORK}/datadir"
|
||||
|
||||
assert_line "${WORK}/agent" "need net"
|
||||
assert_line "${WORK}/agent" "after localmount"
|
||||
assert_line "${WORK}/agent" "before rds-datadir rds-init mariadb"
|
||||
assert_line "${WORK}/datadir" "after localmount rds-agent"
|
||||
assert_line "${WORK}/datadir" "before rds-init mariadb"
|
||||
|
||||
if grep -Fq "before rds-agent" "${WORK}/datadir"; then
|
||||
echo "FAIL: rds-datadir still starts before the bootstrap handoff" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "rds-openrc: dependency order tests passed"
|
||||
@@ -0,0 +1,77 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
# setup.sh — guest customisation for the spinifex-rds-mariadb AMI. Runs inside
|
||||
# the libguestfs appliance under build-system-image.sh, after packages and
|
||||
# INSTALL_FILES are placed.
|
||||
|
||||
# INSTALL_FILES land 0644; OpenRC requires 0755 on init scripts, and rds-datadir
|
||||
# is executed directly by its service.
|
||||
chmod 0755 /etc/init.d/rds-datadir /etc/init.d/rds-agent /usr/local/sbin/rds-datadir
|
||||
chmod 0755 /etc/init.d/mulga-mgmt-net /etc/init.d/mulga-mgmt-net-routes \
|
||||
/usr/local/sbin/mulga-mgmt-net
|
||||
|
||||
# mulga-mgmt-net goes in the boot runlevel, not default (where ENABLE_SERVICES
|
||||
# lands services). It applies mgmt0's static address, which rds-agent needs to
|
||||
# reach the gateway at all, and DHCPs the data NIC so the init-local Ec2 crawl
|
||||
# reaches IMDS; a default entry runs after cloud-init-local and is too late.
|
||||
rc-update add mulga-mgmt-net boot
|
||||
|
||||
# Where cloud-init drops the agent's env file and the gateway CA. Created here
|
||||
# so the delivery lands in a root-only directory.
|
||||
install -d -m 0700 /etc/spinifex-rds
|
||||
|
||||
# The engine this image bakes. rds-agent builds its engine implementation from
|
||||
# this file, so running the wrong implementation is structurally impossible
|
||||
# rather than merely validated against; a VM launched as another engine is
|
||||
# refused before anything touches the datadir.
|
||||
printf 'mariadb\n' > /etc/spinifex-rds/engine
|
||||
chmod 0444 /etc/spinifex-rds/engine
|
||||
|
||||
# Empty in the image: rds-datadir mounts over it at boot. The package already
|
||||
# creates it 0750 mysql:mysql; restated so a packaging change cannot loosen it.
|
||||
install -d -m 0750 -o mysql -g mysql /var/lib/mysql
|
||||
|
||||
# The include target, baked so it exists even when the data volume is not
|
||||
# mounted. MariaDB treats an !includedir it cannot open as a fatal error while
|
||||
# parsing defaults, which would take down the client alongside the server on
|
||||
# exactly the boot an operator needs both to diagnose the missing volume.
|
||||
install -d -m 0750 -o mysql -g mysql /var/lib/mysql/conf.d
|
||||
|
||||
# The generated configuration must be read last, or a packaged default would win
|
||||
# over a platform-owned setting. Sorted in byte order, the way MariaDB itself
|
||||
# walks an include directory, so a future package adding a later-sorting file
|
||||
# fails the build rather than silently overriding the platform.
|
||||
last_dropin=$(ls /etc/my.cnf.d | LC_ALL=C sort | tail -n 1)
|
||||
if [ "${last_dropin}" != "zz-rds-include.cnf" ]; then
|
||||
echo "[rds-mariadb-setup] /etc/my.cnf.d/${last_dropin} is read after the RDS include"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Alpine's packaged server drop-in sets skip-networking, which would leave the
|
||||
# instance reachable only over its unix socket — the endpoint would resolve, the
|
||||
# health probe would pass, and nothing on the customer ENI could ever connect.
|
||||
# Disabled at the source rather than countered from a later file, so the image
|
||||
# does not depend on override ordering for the endpoint to work at all.
|
||||
sed -i 's/^skip-networking$/# skip-networking — disabled: the customer reaches this instance over TCP/' \
|
||||
/etc/my.cnf.d/mariadb-server.cnf
|
||||
if grep -rEq '^[[:space:]]*skip[-_]networking' /etc/my.cnf /etc/my.cnf.d; then
|
||||
echo "[rds-mariadb-setup] a packaged configuration file still disables TCP networking"
|
||||
grep -rEn '^[[:space:]]*skip[-_]networking' /etc/my.cnf /etc/my.cnf.d
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Bind /dev/console to the serial port so userspace boot output reaches ttyS0,
|
||||
# which the orchestrator captures host-side. Linux makes the last console= the
|
||||
# controlling one, and stock Alpine lists tty0 last — reorder so ttyS0 wins.
|
||||
sed -i \
|
||||
's|console=ttyS0,115200n8 console=ttyAMA0,115200n8 console=tty0|console=tty0 console=ttyAMA0,115200n8 console=ttyS0,115200n8|' \
|
||||
/etc/update-extlinux.conf /boot/extlinux.conf
|
||||
|
||||
# Cut the boot-menu countdown from 10s to ~1s, a fixed tax on every VM start.
|
||||
# Both the generator config (seconds) and the rendered output (1/10s) are patched
|
||||
# so a regenerate keeps it; a small nonzero keeps the menu interruptible.
|
||||
sed -i 's/^timeout=.*/timeout=1/' /etc/update-extlinux.conf
|
||||
sed -i 's/^TIMEOUT[[:space:]].*/TIMEOUT 10/' /boot/extlinux.conf
|
||||
|
||||
echo "[rds-mariadb-setup] done"
|
||||
@@ -0,0 +1,19 @@
|
||||
# /etc/my.cnf.d/zz-rds-include.cnf — Spinifex RDS generated-configuration include.
|
||||
#
|
||||
# The generated files live on the data volume rather than under /etc. A class
|
||||
# change is a VM replacement: a fresh boot volume from this AMI with the same
|
||||
# data volume reattached. Parameters under /etc would be discarded by that
|
||||
# replacement and the instance would silently revert to catalog defaults while
|
||||
# the API still reported the customer's values.
|
||||
#
|
||||
# zz- so this is the last drop-in the packaged !includedir reads. MariaDB walks
|
||||
# an include directory in byte order and takes the last occurrence of a setting,
|
||||
# which is what makes a platform-owned value beat a distribution default — and a
|
||||
# digit prefix would sort ahead of the packaged mariadb-server.cnf rather than
|
||||
# after it. setup.sh asserts the resulting order at build time.
|
||||
#
|
||||
# The directory is also baked empty into this image. MariaDB treats an
|
||||
# !includedir it cannot open as a fatal defaults-parsing error, so a boot with no
|
||||
# data volume mounted would otherwise take the client down with the server.
|
||||
|
||||
!includedir /var/lib/mysql/conf.d
|
||||
@@ -23,7 +23,7 @@ INSTALL_BINARIES="bin/rds-agent:/usr/local/bin/rds-agent"
|
||||
# the guest, or mgmt0 never gets its static address and rds-agent cannot reach
|
||||
# the gateway, and the customer ENI never comes up so nothing reaches the DB.
|
||||
INSTALL_FILES="\
|
||||
scripts/images/rds-postgres/rds-datadir:/usr/local/sbin/rds-datadir \
|
||||
scripts/images/common/rds-datadir:/usr/local/sbin/rds-datadir \
|
||||
scripts/images/rds-postgres/rds-datadir.initd:/etc/init.d/rds-datadir \
|
||||
scripts/images/rds-postgres/rds-init:/usr/local/sbin/rds-init \
|
||||
scripts/images/rds-postgres/rds-init.initd:/etc/init.d/rds-init \
|
||||
|
||||
@@ -14,11 +14,16 @@ depend() {
|
||||
}
|
||||
|
||||
start_pre() {
|
||||
# This image's layout, stated here because the mount script is shared by
|
||||
# every engine preset. A delivered RDS_DATA_MOUNT still wins, so the control
|
||||
# plane can move the mount point without rebuilding the image.
|
||||
RDS_DATA_MOUNT="/var/lib/postgresql"
|
||||
RDS_ENGINE_USER="postgres"
|
||||
if [ -f "${AGENT_ENV}" ]; then
|
||||
# shellcheck disable=SC1090
|
||||
. "${AGENT_ENV}"
|
||||
export RDS_HANDOFF_DIR RDS_DATA_MOUNT
|
||||
fi
|
||||
export RDS_HANDOFF_DIR RDS_DATA_MOUNT RDS_ENGINE_USER
|
||||
}
|
||||
|
||||
start() {
|
||||
|
||||
Reference in New Issue
Block a user