fix: override bootstrap.post_init via SPILO_CONFIGURATION to create missing 'postgres' role before Spilo's real post_init.sh runs. Spilo hardcodes ALTER VIEW...OWNER TO postgres with no way to parameterize, which fails since our superuser is PGadmin not postgres.
ci/woodpecker/push/deploy Pipeline was successful

This commit is contained in:
2026-07-29 06:35:24 -07:00
parent b86784fe3a
commit 7529e6cb36
+49 -14
View File
@@ -16,8 +16,11 @@
# NOTE: command: blocks use $$(...) not $(...) — Compose's own variable
# interpolation parses $( as an attempted ${VAR} reference and fails with
# "invalid interpolation format" / "you may need to escape any $ with
# another $". $$ escapes to a literal $ for the shell at runtime. Same
# reasoning applies to $$PGDATA below.
# another $". $$ escapes to a literal $ for the shell at runtime. This
# applies uniformly to EVERY literal $ character anywhere in a command:
# block, including inside heredocs and nested quoting — Compose scans the
# raw text before any shell/heredoc logic ever runs, so quoting context
# doesn't exempt anything from this escaping requirement.
#
# NOTE: ETCD3_HOSTS not ETCD_HOSTS — confirmed against zalando/spilo
# configure_spilo.py: PATRONI_DCS includes both "etcd" (legacy v2 API,
@@ -28,18 +31,28 @@
# v3-API client.
#
# NOTE: "legacy" needs a pg_hba.conf rule permitting REPLICATION-type
# connections, which is a distinct connection class from normal client
# connections in Postgres. The vanilla postgres:17 image's default
# pg_hba.conf allows normal client connections (pg_dumpall worked earlier)
# but NOT replication connections, which pg_basebackup (used by Patroni's
# CLONE_WITH_BASEBACKUP) requires. Fixed via a /docker-entrypoint-initdb.d/
# hook script (the officially supported extension point, runs once right
# after initdb, before the server's final restart) that appends a
# permissive "trust" rule for replication connections. "trust" is
# acceptable ONLY because this container is fully disposable and exists
# solely to validate cutover mechanics, not auth. See the note
# "ADR-0001 Addendum — pg_hba.conf replication prerequisite discovered in
# dry run" for the real production prerequisite this exposes.
# connections (distinct from normal client connections), fixed via a
# /docker-entrypoint-initdb.d/ hook script (official extension point).
# "trust" is acceptable ONLY because this container is fully disposable.
# See note "ADR-0001 Addendum — pg_hba.conf replication prerequisite
# discovered in dry run" for the real production prerequisite this exposes.
#
# NOTE: patroni-0/patroni-1 override bootstrap.post_init via
# SPILO_CONFIGURATION (Spilo's documented, supported mechanism for
# overriding any generated Patroni config — configure_spilo.py deep-merges
# user-supplied SPILO_CONFIGURATION on top of its own generated config).
# This is needed because Spilo's own /scripts/post_init.sh hardcodes
# "ALTER VIEW ... OWNER TO postgres" with no way to parameterize that role
# name via env vars. Since our superuser is PGUSER_SUPERUSER=PGadmin (to
# match production), there is no role literally named "postgres" in the
# cloned data, so Spilo's unmodified script fails with
# 'ERROR: role "postgres" does not exist'. The override points
# bootstrap.post_init at our own wrapper script instead, which creates a
# harmless, idempotent, NOLOGIN "postgres" role first, then execs Spilo's
# real, UNMODIFIED post_init.sh with all original arguments passed through
# — Zalando's script itself is never patched or forked. This affects real
# production too (superuser there is also PGadmin, not postgres) and must
# be carried into the real staging/production compose files as well.
# ─────────────────────────────────────────────────────────────────────────
version: "3.6"
@@ -156,6 +169,14 @@ services:
export PGPASSWORD_STANDBY="$$(cat /run/secrets/postgresql_replication_password)"
export PATRONI_RESTAPI_PASSWORD="$$(cat /run/secrets/postgresql_patroni_password)"
export CLONE_PASSWORD="$$(cat /run/secrets/postgresql_password)"
mkdir -p /scripts
cat > /scripts/post_init_wrapper.sh <<'WRAP'
#!/bin/bash
set -e
psql -d "$$2" -v ON_ERROR_STOP=1 -c 'DO $$do$$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = $$x$$postgres$$x$$) THEN CREATE ROLE postgres; END IF; END $$do$$;'
exec /scripts/post_init.sh "$$@"
WRAP
chmod +x /scripts/post_init_wrapper.sh
exec /bin/sh /launch.sh init
environment:
SCOPE: pgha-test
@@ -170,6 +191,9 @@ services:
CLONE_HOST: postgresql
CLONE_PORT: "5432"
CLONE_USER: PGadmin
SPILO_CONFIGURATION: |
bootstrap:
post_init: /scripts/post_init_wrapper.sh "zalandos"
secrets:
- postgresql_password
- postgresql_replication_password
@@ -194,6 +218,14 @@ services:
export PGPASSWORD_STANDBY="$$(cat /run/secrets/postgresql_replication_password)"
export PATRONI_RESTAPI_PASSWORD="$$(cat /run/secrets/postgresql_patroni_password)"
export CLONE_PASSWORD="$$(cat /run/secrets/postgresql_password)"
mkdir -p /scripts
cat > /scripts/post_init_wrapper.sh <<'WRAP'
#!/bin/bash
set -e
psql -d "$$2" -v ON_ERROR_STOP=1 -c 'DO $$do$$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = $$x$$postgres$$x$$) THEN CREATE ROLE postgres; END IF; END $$do$$;'
exec /scripts/post_init.sh "$$@"
WRAP
chmod +x /scripts/post_init_wrapper.sh
exec /bin/sh /launch.sh init
environment:
SCOPE: pgha-test
@@ -208,6 +240,9 @@ services:
CLONE_HOST: postgresql
CLONE_PORT: "5432"
CLONE_USER: PGadmin
SPILO_CONFIGURATION: |
bootstrap:
post_init: /scripts/post_init_wrapper.sh "zalandos"
secrets:
- postgresql_password
- postgresql_replication_password