mirror of
https://github.com/kevinveenbirkenbach/docker-volume-backup.git
synced 2026-07-21 16:04:15 +00:00
fix(backup,restore): make restore drills replayable and leave swarm tasks alone
Restore fixes, both hit by the infinito svc-bkp e2e drill: - mariadb --empty dropped tables one docker exec at a time with SET FOREIGN_KEY_CHECKS=0 issued in a separate client session, so the session-scoped toggle never applied and any FK-referenced parent table (mailu.users) died with ERROR 1451. Issue the toggle and all DROPs in one session. - postgres --empty now drops only current_user-owned objects (extension members like pg_trgm's set_limit are superuser-owned) with IF EXISTS absorbing CASCADE fallout, and the replay skips superuser-only dump lines (COMMENT ON EXTENSION, ALTER DEFAULT PRIVILEGES) that abort an app-user psql run under ON_ERROR_STOP. Backup fixes: - pg_dump now runs with --no-owner --no-privileges so future dumps are replayable by the owning app user in the first place. - Swarm task containers are never stopped or started manually: the orchestrator replaces a stopped task and a later docker start fails on the detached overlay network. filter_stoppable skips them visibly and the whitelist stop check ignores them. Validated end to end against a live infinito compose stack: the full svc-bkp-volume-2-local drill (verify, restore cycle, sql replay for mailu, keycloak and one more db) passes with these patches applied. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,17 +21,43 @@ def restore_postgres_sql(
|
||||
docker_env = {"PGPASSWORD": password}
|
||||
|
||||
if empty:
|
||||
# Owner-filtered: extension members (pg_trgm's set_limit) are superuser-owned; IF EXISTS absorbs CASCADE fallout.
|
||||
drop_sql = r"""
|
||||
DO $$ DECLARE r RECORD;
|
||||
BEGIN
|
||||
FOR r IN (
|
||||
SELECT table_name AS name, 'TABLE' AS type FROM information_schema.tables WHERE table_schema='public'
|
||||
SELECT c.relname AS name,
|
||||
CASE c.relkind
|
||||
WHEN 'v' THEN 'VIEW'
|
||||
WHEN 'm' THEN 'MATERIALIZED VIEW'
|
||||
WHEN 'f' THEN 'FOREIGN TABLE'
|
||||
ELSE 'TABLE'
|
||||
END AS type
|
||||
FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
|
||||
WHERE n.nspname = 'public' AND c.relkind IN ('r', 'p', 'v', 'm', 'f')
|
||||
AND pg_get_userbyid(c.relowner) = current_user
|
||||
UNION ALL
|
||||
SELECT routine_name AS name, 'FUNCTION' AS type FROM information_schema.routines WHERE specific_schema='public'
|
||||
SELECT p.proname AS name,
|
||||
CASE p.prokind WHEN 'p' THEN 'PROCEDURE' ELSE 'FUNCTION' END AS type
|
||||
FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace
|
||||
WHERE n.nspname = 'public' AND p.prokind IN ('f', 'p', 'w')
|
||||
AND pg_get_userbyid(p.proowner) = current_user
|
||||
UNION ALL
|
||||
SELECT sequence_name AS name, 'SEQUENCE' AS type FROM information_schema.sequences WHERE sequence_schema='public'
|
||||
SELECT c.relname AS name, 'SEQUENCE' AS type
|
||||
FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
|
||||
WHERE n.nspname = 'public' AND c.relkind = 'S'
|
||||
AND pg_get_userbyid(c.relowner) = current_user
|
||||
UNION ALL
|
||||
SELECT t.typname AS name, 'TYPE' AS type
|
||||
FROM pg_type t JOIN pg_namespace n ON n.oid = t.typnamespace
|
||||
WHERE n.nspname = 'public'
|
||||
AND pg_get_userbyid(t.typowner) = current_user
|
||||
AND (t.typtype IN ('e', 'd')
|
||||
OR (t.typtype = 'c' AND EXISTS (
|
||||
SELECT 1 FROM pg_class c2
|
||||
WHERE c2.oid = t.typrelid AND c2.relkind = 'c')))
|
||||
) LOOP
|
||||
EXECUTE format('DROP %s public.%I CASCADE', r.type, r.name);
|
||||
EXECUTE format('DROP %s IF EXISTS public.%I CASCADE', r.type, r.name);
|
||||
END LOOP;
|
||||
END $$;
|
||||
"""
|
||||
@@ -43,11 +69,18 @@ END $$;
|
||||
)
|
||||
|
||||
with open(sql_path, "rb") as f:
|
||||
docker_exec(
|
||||
container,
|
||||
["psql", "-v", "ON_ERROR_STOP=1", "-U", user, "-d", db_name],
|
||||
stdin=f,
|
||||
docker_env=docker_env,
|
||||
)
|
||||
raw_sql = f.read()
|
||||
# COMMENT ON EXTENSION and ALTER DEFAULT PRIVILEGES are superuser-only;
|
||||
# app-level restores must skip them or ON_ERROR_STOP aborts the replay.
|
||||
superuser_only = (b"COMMENT ON EXTENSION", b"ALTER DEFAULT PRIVILEGES")
|
||||
sql = b"\n".join(
|
||||
line for line in raw_sql.splitlines() if not line.startswith(superuser_only)
|
||||
)
|
||||
docker_exec(
|
||||
container,
|
||||
["psql", "-v", "ON_ERROR_STOP=1", "-U", user, "-d", db_name],
|
||||
stdin=sql,
|
||||
docker_env=docker_env,
|
||||
)
|
||||
|
||||
print(f"PostgreSQL restore complete for db '{db_name}'.")
|
||||
|
||||
Reference in New Issue
Block a user