diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/common.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/common.sh index 297a234bad..1b25ec875c 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/common.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/common.sh @@ -69,6 +69,66 @@ function run_sql { psql -h localhost -U supabase_admin -d postgres "$@" } +# TODO: Drop once all projects have been upgraded past 17.6.1.111, 15.14.1.111 +# +# Old projects still have grant-access event triggers scoped to the tags +# their original seed used while the grant functions themselves (updated +# fleet-wide) only act on CREATE EXTENSION events. +# +# * issue_pg_graphql_access on CREATE FUNCTION before build 17.6.1.111 / +# 15.14.1.111 +# * issue_pg_cron_access on CREATE SCHEMA before 15.1.0.130 +# +# With this mismatch, recreating the extension never re-applies its wiring +# (graphql_public.graphql wrapper, cron grants). Rescope the triggers so +# the CREATE EXTENSION statements that re-enable the extensions after +# pg_upgrade fire them. +# +# Trigger definitions kept identical to migrations +# 20260421000001_rescope_pg_graphql_access_trigger.sql and +# 20231020085357_revoke_writes_on_cron_job_from_postgres.sql. This is a +# no-op on projects that already have the CREATE EXTENSION scope. +function rescope_extension_event_triggers { + local sql + sql=$( + cat <<-'EOF' + do $rescope$ + begin + if exists ( + select 1 + from pg_proc p + join pg_namespace n on p.pronamespace = n.oid + where n.nspname = 'extensions' and p.proname = 'grant_pg_graphql_access' + ) then + execute 'drop event trigger if exists issue_pg_graphql_access'; + execute $q$ + create event trigger issue_pg_graphql_access + on ddl_command_end + when tag in ('CREATE EXTENSION') + execute procedure extensions.grant_pg_graphql_access() + $q$; + end if; + + if exists ( + select 1 + from pg_proc p + join pg_namespace n on p.pronamespace = n.oid + where n.nspname = 'extensions' and p.proname = 'grant_pg_cron_access' + ) then + execute 'drop event trigger if exists issue_pg_cron_access'; + execute $q$ + create event trigger issue_pg_cron_access + on ddl_command_end + when tag in ('CREATE EXTENSION') + execute procedure extensions.grant_pg_cron_access() + $q$; + end if; + end $rescope$; + EOF + ) + run_sql -c "$sql" +} + # Wrap a db name in dbname='...' (escaping \ and ') so characters special to -d # parsing (=, spaces, quotes) stay part of a literal name, not a conninfo fragment. # psql parses a -d value containing '=' as a full conninfo string, so a customer diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh index ccbe7503eb..7bc6eaebe1 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh @@ -204,6 +204,13 @@ ALTER SYSTEM SET jit = off; SELECT pg_reload_conf(); EOF + # Rescope before dropping extensions: the fixed triggers are carried into the new + # cluster by pg_upgrade, so both the post-upgrade re-enable and the failure-path + # re-enable below fire them. Fail-soft: a broken rescope should not block the + # upgrade — but retry first, since a skipped rescope means the recreation of + # these extensions silently loses their wiring. + retry 3 rescope_extension_event_triggers || log "WARNING: failed to rescope extension event triggers" + # Disable extensions if they're enabled # Generate SQL script to re-enable them after upgrade for EXTENSION in "${EXTENSIONS_TO_DISABLE[@]}"; do diff --git a/tests/pg_upgrade/tests/01-schema.sql b/tests/pg_upgrade/tests/01-schema.sql index 3cf3a83ddf..8b03dc64f8 100644 --- a/tests/pg_upgrade/tests/01-schema.sql +++ b/tests/pg_upgrade/tests/01-schema.sql @@ -1,7 +1,7 @@ CREATE EXTENSION IF NOT EXISTS pgtap; BEGIN; -SELECT plan(15); +SELECT plan(18); select has_schema('public'); select has_schema('auth'); @@ -22,5 +22,18 @@ SELECT has_column('public', 'countries', 'continent', 'Column continent should e SELECT has_materialized_view('public', 'european_countries', 'Materialized view european_countries should exist'); +SELECT is( + (SELECT evttags FROM pg_event_trigger WHERE evtname = 'issue_pg_graphql_access'), + ARRAY['CREATE EXTENSION']::text[], + 'issue_pg_graphql_access should be rescoped to CREATE EXTENSION by the upgrade'); +SELECT is( + (SELECT evttags FROM pg_event_trigger WHERE evtname = 'issue_pg_cron_access'), + ARRAY['CREATE EXTENSION']::text[], + 'issue_pg_cron_access should be rescoped to CREATE EXTENSION by the upgrade'); +SELECT ok( + (SELECT prosrc LIKE '%graphql.resolve%' FROM pg_proc + WHERE proname = 'graphql' AND pronamespace = 'graphql_public'::regnamespace), + 'graphql_public.graphql should be the real wrapper after upgrade, not the placeholder'); + SELECT * FROM finish(); ROLLBACK; diff --git a/tests/pg_upgrade/tests/99-fixtures.sql b/tests/pg_upgrade/tests/99-fixtures.sql index 2b93d459a2..5e5315ea45 100644 --- a/tests/pg_upgrade/tests/99-fixtures.sql +++ b/tests/pg_upgrade/tests/99-fixtures.sql @@ -10,3 +10,19 @@ with no data; refresh materialized view public.european_countries; select count(*) from public.european_countries; + +-- simulate a project created before the trigger rescope migrations: the +-- fleet-wide function updates left these projects with triggers still scoped +-- to their original tags, so recreating the extensions no longer re-applies +-- their wiring (graphql_public.graphql wrapper, cron grants). The upgrade +-- must rescope both to CREATE EXTENSION. +drop event trigger if exists issue_pg_graphql_access; +create event trigger issue_pg_graphql_access + on ddl_command_end + when tag in ('CREATE FUNCTION') + execute procedure extensions.grant_pg_graphql_access(); +drop event trigger if exists issue_pg_cron_access; +create event trigger issue_pg_cron_access + on ddl_command_end + when tag in ('CREATE SCHEMA') + execute procedure extensions.grant_pg_cron_access();