hack/postgres-release-system-databases.bats
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env bats
# Behavioural tests for the "release system databases" pass of the Postgres
# init script:
#
# packages/apps/postgres/templates/init-script.yaml
#
# The pass is cut out of the rendered script and RUN against a fake psql that
# records which database each session connects to. The helm-unittest suite in
# packages/apps/postgres/tests/system_databases_test.yaml matches the SQL text,
# and stayed green when the template0 guard was inverted: the rendered pass then
# connected to template0, which accepts no connection, and skipped postgres and
# template1, which are the two it exists to hand back.
#
# What is pinned here:
# * no session ever connects to template0;
# * postgres and template1 are each connected to and released;
# * every taken-over system database, template0 included, has its comment
# cleared;
# * releasing template1 visits every other connectable database, for the
# trigger it passed on to them, and a failed lookup of those databases
# stops the pass before the comment that selects template1 is cleared;
# * the session that releases a system database hands its public schema
# and the database itself back only from a marked <db>_admin or
# <db>_readonly owner (or cozystack:orphaned), before the reassignment,
# an inherited public only from a marked template1_admin, and touches
# only the roles that carry the
# "cozystack:managed" mark, not a role that merely has one of the names.
#
# Run from the repo root with make bats-unit-tests. Direct status checks
# retain compatibility with cozytest, which has no Bats run/setup helpers.
# Inline cleanup preserves Bats' TAP handler on failed assertions.
load test_helper
PG_CHART=packages/apps/postgres
# render_pass <out>
# Extract the release pass, from its banner to the next one, out of the rendered
# init script. The non-empty check must be an early `return 1`: hack/cozytest.sh
# rewrites every line matching ^}$ into `return 0` plus `}`, helpers included,
# so a check left in last-command position has its status discarded.
render_pass() {
helm template pg-test "$PG_CHART" --namespace tenant-test --set-json '_cluster={"scheduling":{}}' 2>/dev/null \
| yq eval 'select(.metadata.name == "pg-test-init-script") | .stringData["init.sh"]' - \
| awk '/^echo "== release system databases"/ { f = 1 } /^echo "== delete databases"/ { f = 0 } f' > "$1"
grep -q 'SYSTEM_DBS=' "$1" || return 1
}
# write_fake_psql <dir>
# Emit a fake psql. A `-c` query answers from $SYSTEM_DBS for the system
# database lookup and from $OTHER_DBS for the connectable-database lookup,
# which exits with $CLONE_LOOKUP_FAILS instead when that is set. A
# session fed on stdin logs "connect <dbname>" ("connect -" without --dbname)
# followed by its first line and every -v db= value, to $PLOG, and its whole
# input under a "-- session <dbname> db=<value>" header to $PLOG.sql.
# No line below is a bare column-0 `}`, so cozytest.sh's parser leaves it alone.
write_fake_psql() {
cat > "$1/psql" <<'PEOF'
#!/bin/sh
dbname=- query= vdb=
while [ $# -gt 0 ]; do
case "$1" in
--dbname) dbname=$2; shift ;;
-c) query=$2; shift ;;
-v) case "$2" in db=*) vdb=${2#db=} ;; esac; shift ;;
esac
shift
done
if [ -n "$query" ]; then
case "$query" in
*"d.datname IN ('postgres', 'template0', 'template1')"*) printf '%s\n' $SYSTEM_DBS ;;
*datallowconn*)
if [ -n "${CLONE_LOOKUP_FAILS:-}" ]; then
echo "fake psql: connectable-database lookup failed" >&2
exit "$CLONE_LOOKUP_FAILS"
fi
printf '%s\n' $OTHER_DBS ;;
*) echo "fake psql: unexpected query: $query" >&2; exit 64 ;;
esac
exit 0
fi
sql=$(cat)
echo "connect $dbname db=$vdb $(printf '%s\n' "$sql" | head -n 1)" >> "$PLOG"
printf -- '-- session %s db=%s\n%s\n' "$dbname" "$vdb" "$sql" >> "$PLOG.sql"
PEOF
chmod +x "$1/psql"
}
# run_pass <dir>
# Run the extracted pass with the fake psql first on PATH; status in $rc.
run_pass() {
export PLOG="$1/calls"
: > "$PLOG"
rc=0
PATH="$1:$PATH" sh -e "$1/pass.sh" > "$1/out" 2> "$1/err" || rc=$?
}
@test "a render that produces no release pass fails instead of passing as a no-op" {
tmp=$(mktemp -d)
if PG_CHART=packages/apps/no-such-chart render_pass "$tmp/pass.sh"; then
echo "FAIL: render_pass reported success for a chart that does not exist"
false
fi
rm -rf "$tmp"
}
@test "release pass connects to postgres and template1, never to template0" {
tmp=$(mktemp -d)
write_fake_psql "$tmp"
render_pass "$tmp/pass.sh"
export SYSTEM_DBS="postgres template0 template1" OTHER_DBS="postgres app"
run_pass "$tmp"
if [ "$rc" -ne 0 ]; then
echo "FAIL: the pass exited $rc"
cat "$tmp/out" "$tmp/err"
false
fi
if grep -q '^connect template0 ' "$tmp/calls"; then
echo "FAIL: the pass connected to template0, which accepts no connection"
cat "$tmp/calls"
false
fi
for db in postgres template1; do
if ! grep -q "^connect $db db=$db DROP EVENT TRIGGER IF EXISTS trigger_auto_grant;" "$tmp/calls"; then
echo "FAIL: $db was not connected to and released"
cat "$tmp/calls"
false
fi
done
for db in postgres template0 template1; do
if ! grep -q "^connect - db=$db COMMENT ON DATABASE :\"db\" IS NULL;" "$tmp/calls"; then
echo "FAIL: the comment on $db was not cleared"
cat "$tmp/calls"
false
fi
done
rm -rf "$tmp"
}
@test "release session hands public back and touches only marked roles" {
tmp=$(mktemp -d)
write_fake_psql "$tmp"
render_pass "$tmp/pass.sh"
export SYSTEM_DBS="postgres" OTHER_DBS="postgres"
run_pass "$tmp"
if [ "$rc" -ne 0 ]; then
echo "FAIL: the pass exited $rc"
cat "$tmp/out" "$tmp/err"
false
fi
awk '/^-- session / { f = ($0 == "-- session postgres db=postgres") } f' "$tmp/calls.sql" > "$tmp/release.sql"
# Each statement must end on its whole predicate: a clause appended after the
# mark (an OR, say) would select an unmarked role again.
reassign_where="WHERE EXISTS (SELECT FROM pg_auth_members m JOIN pg_roles g ON g.oid = m.roleid JOIN pg_roles u ON u.oid = m.member WHERE g.rolname = 'cozystack:managed' AND u.rolname = r)\\gexec"
owner_marked="(o.rolname = 'cozystack:orphaned' OR o.rolname IN (:'db' || '_admin', :'db' || '_readonly') AND EXISTS (SELECT FROM pg_auth_members m JOIN pg_roles g ON g.oid = m.roleid WHERE g.rolname = 'cozystack:managed' AND m.member = o.oid))\\gexec"
public_where="WHERE n.nspname = 'public' AND $owner_marked"
database_where="WHERE d.datname = :'db' AND $owner_marked"
public_line=$(grep -n 'ALTER SCHEMA public OWNER TO' "$tmp/release.sql" | head -1)
database_line=$(grep -n 'ALTER DATABASE' "$tmp/release.sql" | head -1)
reassign_line=$(grep -n 'REASSIGN OWNED BY' "$tmp/release.sql" | head -1)
if [ "${public_line##*"$public_where"}" != "" ] || [ -z "$public_line" ]; then
echo "FAIL: the release session does not hand public back only from a marked <db>_admin or <db>_readonly owner"
cat "$tmp/release.sql"
false
fi
if [ "${database_line##*"$database_where"}" != "" ] || [ -z "$database_line" ]; then
echo "FAIL: the release session does not take the database back only from a marked owner"
cat "$tmp/release.sql"
false
fi
if [ "${reassign_line##*"$reassign_where"}" != "" ] || [ -z "$reassign_line" ]; then
echo "FAIL: the release session does not reassign only roles carrying the cozystack:managed mark"
cat "$tmp/release.sql"
false
fi
# public and the database must be handed back before REASSIGN OWNED would
# move them to cozystack:orphaned.
if [ "${public_line%%:*}" -ge "${reassign_line%%:*}" ] || [ "${database_line%%:*}" -ge "${reassign_line%%:*}" ]; then
echo "FAIL: public or the database is handed back after the reassignment has already moved it"
cat "$tmp/release.sql"
false
fi
rm -rf "$tmp"
}
@test "release pass of template1 visits every other connectable database" {
tmp=$(mktemp -d)
write_fake_psql "$tmp"
render_pass "$tmp/pass.sh"
export SYSTEM_DBS="template1" OTHER_DBS="postgres app"
run_pass "$tmp"
if [ "$rc" -ne 0 ]; then
echo "FAIL: the pass exited $rc"
cat "$tmp/out" "$tmp/err"
false
fi
for db in postgres app; do
if ! grep -q "^connect $db db= SELECT 'DROP EVENT TRIGGER IF EXISTS trigger_auto_grant'" "$tmp/calls"; then
echo "FAIL: $db was not checked for the trigger inherited from template1"
cat "$tmp/calls"
false
fi
done
# An inherited public changes hands only from a template1_admin the chart
# marked; an owner that merely has the name keeps it.
inherited_where="WHERE n.nspname = 'public' AND o.rolname = 'template1_admin' AND EXISTS (SELECT FROM pg_auth_members m JOIN pg_roles g ON g.oid = m.roleid WHERE g.rolname = 'cozystack:managed' AND m.member = o.oid)\\gexec"
inherited_line=$(awk '/^-- session / { f = ($0 == "-- session app db=") } f' "$tmp/calls.sql" | grep 'ALTER SCHEMA public OWNER TO' | head -1)
if [ -z "$inherited_line" ] || [ "${inherited_line##*"$inherited_where"}" != "" ]; then
echo "FAIL: an inherited public is handed back without checking that a marked template1_admin owns it"
cat "$tmp/calls.sql"
false
fi
rm -rf "$tmp"
}
@test "release pass of template1 stops before clearing its comment when the clone lookup fails" {
# Clearing the comment is what stops later runs from selecting template1, so
# a lookup failure that let the pass go on would skip the clone repair for
# good.
tmp=$(mktemp -d)
write_fake_psql "$tmp"
render_pass "$tmp/pass.sh"
export SYSTEM_DBS="template1" OTHER_DBS="postgres app" CLONE_LOOKUP_FAILS=73
run_pass "$tmp"
unset CLONE_LOOKUP_FAILS
if [ "$rc" -ne 73 ]; then
echo "FAIL: the pass exited $rc instead of stopping on the failed lookup (73)"
cat "$tmp/out" "$tmp/err" "$tmp/calls"
false
fi
if grep -q 'COMMENT ON DATABASE' "$tmp/calls"; then
echo "FAIL: the comment on template1 was cleared although its clones were never visited"
cat "$tmp/calls"
false
fi
rm -rf "$tmp"
}
@test "release pass of postgres alone leaves the other databases alone" {
tmp=$(mktemp -d)
write_fake_psql "$tmp"
render_pass "$tmp/pass.sh"
export SYSTEM_DBS="postgres" OTHER_DBS="postgres app"
run_pass "$tmp"
if [ "$rc" -ne 0 ]; then
echo "FAIL: the pass exited $rc"
cat "$tmp/out" "$tmp/err"
false
fi
if grep -q '^connect app ' "$tmp/calls"; then
echo "FAIL: the pass visited app although template1 was not taken over"
cat "$tmp/calls"
false
fi
rm -rf "$tmp"
}