Apply tracked schema migrations before deployment and test runtime grants
Foundry Go checks / check (push) Successful in 1m48s
Foundry Go checks / check (pull_request) Failing after 1m38s

This commit is contained in:
2026-11-05 18:43:45 +08:00
parent 4a06944fec
commit 206cd45098
4 changed files with 84 additions and 2 deletions
+1
View File
@@ -8,6 +8,7 @@ on:
- 'go.mod'
- 'go.sum'
- 'deploy/**'
- 'db/**'
- '.gitea/workflows/deploy.yml'
concurrency:
+6 -2
View File
@@ -19,7 +19,11 @@ VALUES ('fixture','00000000-0000-0000-0000-000000000001','existing-google','exis
INSERT INTO project_sessions VALUES
('existing-session','fixture','00000000-0000-0000-0000-000000000001',now()+interval '1 hour');
SQL
psql < db/002-platform.sql
psql <<'SQLROLE'
CREATE ROLE foundry_fixture LOGIN;
SQLROLE
python3 deploy/migrate.py --container "$container" --database postgres --role foundry_fixture
python3 deploy/migrate.py --container "$container" --database postgres --role foundry_fixture
psql <<'SQL'
DO $$
BEGIN
@@ -60,5 +64,5 @@ CGO_ENABLED=0 go test -mod=readonly -c -o bin/platform-tests ./internal/platform
docker run --rm --network "container:$container" --user 1000:1000 \
--read-only --cap-drop ALL --security-opt no-new-privileges \
--mount "type=bind,source=$(pwd)/bin/platform-tests,target=/opt/platform-tests,readonly" \
-e PLATFORM_TEST_DATABASE_URL=postgres://postgres@127.0.0.1:5432/postgres?sslmode=disable \
-e PLATFORM_TEST_DATABASE_URL=postgres://foundry_fixture@127.0.0.1:5432/postgres?sslmode=disable \
--entrypoint /opt/platform-tests postgres:17-alpine -test.v
+76
View File
@@ -0,0 +1,76 @@
#!/usr/bin/env python3
"""Apply checked migrations atomically from the trusted deployment runner."""
import argparse
import hashlib
from pathlib import Path
import re
import subprocess
parser = argparse.ArgumentParser()
parser.add_argument("--container", default="endoleap-dev-postgres-1")
parser.add_argument("--database", default="foundry_auth")
parser.add_argument("--role", default="foundry")
args = parser.parse_args()
for value in (args.database, args.role):
if not re.fullmatch(r"[a-z_][a-z0-9_]{0,62}", value):
raise SystemExit("Invalid deployment database or role")
migrations = sorted((Path(__file__).resolve().parent.parent / "db").glob("[0-9]*.sql"))
if not migrations:
raise SystemExit("No migrations found")
script = [
"\\set ON_ERROR_STOP on",
"BEGIN;",
"SET LOCAL lock_timeout='30s';",
"SELECT pg_advisory_xact_lock(731920164);",
"CREATE TABLE IF NOT EXISTS foundry_schema_migrations "
"(name text PRIMARY KEY, checksum text NOT NULL, applied_at timestamptz NOT NULL DEFAULT now());",
]
tables = set()
for index, path in enumerate(migrations):
source = path.read_text()
name = path.name
if not re.fullmatch(r"[0-9]+-[a-z0-9-]+\.sql", name):
raise SystemExit("Invalid migration name")
checksum = hashlib.sha256(source.encode()).hexdigest()
tables.update(re.findall(r"CREATE TABLE ([a-z_]+)", source))
# The first deployed version predates migration tracking.
if index == 0:
script.extend([
"SELECT bool_and(to_regclass('public.' || name) IS NOT NULL) AS baseline FROM "
"unnest(ARRAY['project_accounts','oauth_attempts','login_grants','project_sessions']) name \\gset",
"\\if :baseline",
f"INSERT INTO foundry_schema_migrations(name,checksum) VALUES('{name}','{checksum}') ON CONFLICT DO NOTHING;",
"\\endif",
])
script.extend([
f"SELECT EXISTS(SELECT 1 FROM foundry_schema_migrations WHERE name='{name}') AS applied, "
f"EXISTS(SELECT 1 FROM foundry_schema_migrations WHERE name='{name}' AND checksum='{checksum}') AS valid \\gset",
"\\if :applied",
"\\if :valid",
"\\else",
"\\echo Migration checksum mismatch",
"\\quit 1",
"\\endif",
"\\else",
])
source = source.strip()
if not source.startswith("BEGIN;") or not source.endswith("COMMIT;"):
raise SystemExit("Migration must have explicit transaction boundaries")
script.append(source[len("BEGIN;"):-len("COMMIT;")])
script.extend([
f"INSERT INTO foundry_schema_migrations(name,checksum) VALUES('{name}','{checksum}');",
"\\endif",
])
for table in sorted(tables):
script.append(f'GRANT SELECT,INSERT,UPDATE,DELETE ON TABLE "{table}" TO "{args.role}";')
script.extend([
f'GRANT USAGE,SELECT ON SEQUENCE link_clicks_id_seq TO "{args.role}";',
"COMMIT;",
])
subprocess.run(
["docker", "exec", "-i", args.container, "sh", "-c",
'exec psql -X -U "${POSTGRES_USER:-postgres}" -d "$1" -v ON_ERROR_STOP=1', "sh", args.database],
input=("\n".join(script) + "\n").encode(), check=True,
)
print("Schema migrations and application grants verified.")
+1
View File
@@ -40,6 +40,7 @@ export FOUNDRY_PROJECTS
test -s "$foundry/database.env"
docker network inspect "$network" >/dev/null
python3 "$(dirname "$0")/migrate.py"
release=$(mktemp -d "$foundry/release.XXXXXXXX")
install -m 755 "$1" "$release/foundry"