From 848d4b2c94d2395d024c5ce03f06d50ab50472c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Roche?= Date: Fri, 14 Nov 2025 21:05:05 +0100 Subject: [PATCH] feat: support multiple versions of the supautils extension --- nix/ext/supautils.nix | 101 ++++++++++--- nix/ext/tests/supautils.nix | 139 ++++++++++++++++++ nix/ext/versions.json | 9 ++ nix/tests/expected/roles.out | 6 +- nix/tests/expected/z_15_ext_interface.out | 3 +- nix/tests/expected/z_15_supautils.out | 39 +++++ nix/tests/expected/z_17_ext_interface.out | 3 +- nix/tests/expected/z_17_supautils.out | 39 +++++ .../expected/z_orioledb-17_ext_interface.out | 3 +- nix/tests/sql/z_15_supautils.sql | 25 ++++ nix/tests/sql/z_17_supautils.sql | 25 ++++ 11 files changed, 367 insertions(+), 25 deletions(-) create mode 100644 nix/ext/tests/supautils.nix create mode 100644 nix/tests/expected/z_15_supautils.out create mode 100644 nix/tests/expected/z_17_supautils.out create mode 100644 nix/tests/sql/z_15_supautils.sql create mode 100644 nix/tests/sql/z_17_supautils.sql diff --git a/nix/ext/supautils.nix b/nix/ext/supautils.nix index e849c20dd..6a276c60b 100644 --- a/nix/ext/supautils.nix +++ b/nix/ext/supautils.nix @@ -1,34 +1,97 @@ { + pkgs, lib, stdenv, fetchFromGitHub, postgresql, }: - -stdenv.mkDerivation rec { +let pname = "supautils"; - version = "3.0.1"; - buildInputs = [ postgresql ]; + # Load version configuration from external file + allVersions = (builtins.fromJSON (builtins.readFile ./versions.json)).${pname}; - src = fetchFromGitHub { - owner = "supabase"; - repo = pname; - rev = "refs/tags/v${version}"; - hash = "sha256-j0iASDzmcZRLbHaS9ZNRWwzii7mcC+8wYHM0/mOLkbs="; - }; + # Filter versions compatible with current PostgreSQL version + supportedVersions = lib.filterAttrs ( + _: value: builtins.elem (lib.versions.major postgresql.version) value.postgresql + ) allVersions; + + # Derived version information + versions = lib.naturalSort (lib.attrNames supportedVersions); + latestVersion = lib.last versions; + numberOfVersions = builtins.length versions; + packages = builtins.attrValues ( + lib.mapAttrs (name: value: build name value.hash) supportedVersions + ); + + # Build function for individual versions + build = + version: hash: + stdenv.mkDerivation rec { + inherit pname version; + + buildInputs = [ postgresql ]; + + src = fetchFromGitHub { + owner = "supabase"; + repo = pname; + rev = "refs/tags/v${version}"; + inherit hash; + }; + + installPhase = '' + runHook preInstall + + mkdir -p $out/{lib,share/postgresql/extension} + + # Install shared library with version suffix + mv ${pname}${postgresql.dlSuffix} $out/lib/${pname}-${version}${postgresql.dlSuffix} + + # Create version-specific control file + cat < $out/share/postgresql/extension/${pname}--${version}.control + module_pathname = '$libdir/supautils' + relocatable = false + EOF + + runHook postInstall + ''; - installPhase = '' - mkdir -p $out/lib + meta = with lib; { + description = "PostgreSQL extension for enhanced security"; + homepage = "https://github.com/supabase/${pname}"; + maintainers = with maintainers; [ steve-chavez ]; + platforms = postgresql.meta.platforms; + license = licenses.postgresql; + }; + }; +in +pkgs.buildEnv { + name = pname; + paths = packages; + pathsToLink = [ + "/lib" + "/share/postgresql/extension" + ]; + postBuild = '' + # Create symlinks to latest version for library and control file + ln -sfn ${pname}-${latestVersion}${postgresql.dlSuffix} $out/lib/${pname}${postgresql.dlSuffix} - install -D *${postgresql.dlSuffix} -t $out/lib + # Create default control file pointing to latest + { + echo "default_version = '${latestVersion}'" + cat $out/share/postgresql/extension/${pname}--${latestVersion}.control + } > $out/share/postgresql/extension/${pname}.control ''; - meta = with lib; { - description = "PostgreSQL extension for enhanced security"; - homepage = "https://github.com/supabase/${pname}"; - maintainers = with maintainers; [ steve-chavez ]; - platforms = postgresql.meta.platforms; - license = licenses.postgresql; + passthru = { + inherit versions numberOfVersions; + pname = "${pname}-all"; + defaultSettings = { + session_preload_libraries = "supautils"; + "supautils.disable_program" = "true"; + "supautils.privileged_role" = "privileged_role"; + }; + version = + "multi-" + lib.concatStringsSep "-" (map (v: lib.replaceStrings [ "." ] [ "-" ] v) versions); }; } diff --git a/nix/ext/tests/supautils.nix b/nix/ext/tests/supautils.nix new file mode 100644 index 000000000..0da8d56c8 --- /dev/null +++ b/nix/ext/tests/supautils.nix @@ -0,0 +1,139 @@ +{ self, pkgs }: +let + pname = "supautils"; + inherit (pkgs) lib; + installedExtension = + postgresMajorVersion: self.packages.${pkgs.system}."psql_${postgresMajorVersion}/exts/${pname}-all"; + versions = postgresqlMajorVersion: (installedExtension postgresqlMajorVersion).versions; + postgresqlWithExtension = + postgresql: + let + majorVersion = lib.versions.major postgresql.version; + pkg = pkgs.buildEnv { + name = "postgresql-${majorVersion}-${pname}"; + paths = [ + postgresql + postgresql.lib + (installedExtension majorVersion) + ]; + passthru = { + inherit (postgresql) version psqlSchema; + lib = pkg; + withPackages = _: pkg; + }; + nativeBuildInputs = [ pkgs.makeWrapper ]; + pathsToLink = [ + "/" + "/bin" + "/lib" + ]; + postBuild = '' + wrapProgram $out/bin/postgres --set NIX_PGLIBDIR $out/lib + wrapProgram $out/bin/pg_ctl --set NIX_PGLIBDIR $out/lib + wrapProgram $out/bin/pg_upgrade --set NIX_PGLIBDIR $out/lib + ''; + }; + in + pkg; + psql_15 = postgresqlWithExtension self.packages.${pkgs.system}.postgresql_15; + psql_17 = postgresqlWithExtension self.packages.${pkgs.system}.postgresql_17; +in +self.inputs.nixpkgs.lib.nixos.runTest { + name = pname; + hostPkgs = pkgs; + nodes.server = + { config, ... }: + { + services.postgresql = { + enable = true; + package = (postgresqlWithExtension psql_15); + settings = { + shared_preload_libraries = "supautils"; + "supautils.privileged_extensions" = "address_standardizer, address_standardizer_data_us, autoinc, bloom, btree_gin, btree_gist, citext, cube, dblink, dict_int, dict_xsyn, earthdistance, fuzzystrmatch, hstore, http, hypopg, index_advisor, insert_username, intarray, isn, ltree, moddatetime, orioledb, pg_buffercache, pg_cron, pg_graphql, pg_hashids, pg_jsonschema, pg_net, pg_prewarm, pg_repack, pg_stat_monitor, pg_stat_statements, pg_tle, pg_trgm, pg_walinspect, pgaudit, pgcrypto, pgjwt, pgroonga, pgroonga_database, pgrouting, pgrowlocks, pgsodium, pgstattuple, pgtap, plcoffee, pljava, plls, plpgsql_check, plv8, postgis, postgis_raster, postgis_sfcgal, postgis_tiger_geocoder, postgis_topology, postgres_fdw, refint, rum, seg, sslinfo, supabase_vault, supautils, tablefunc, tcn, timescaledb, tsm_system_rows, tsm_system_time, unaccent, uuid-ossp, vector, wrappers"; + }; + }; + + specialisation.postgresql17.configuration = { + services.postgresql = { + package = lib.mkForce psql_17; + settings = { + "supautils.privileged_extensions" = lib.mkForce "address_standardizer, address_standardizer_data_us, autoinc, bloom, btree_gin, btree_gist, citext, cube, dblink, dict_int, dict_xsyn, earthdistance, fuzzystrmatch, hstore, http, hypopg, index_advisor, insert_username, intarray, isn, ltree, moddatetime, orioledb, pg_buffercache, pg_cron, pg_graphql, pg_hashids, pg_jsonschema, pg_net, pg_prewarm, pg_repack, pg_stat_monitor, pg_stat_statements, pg_tle, pg_trgm, pg_walinspect, pgaudit, pgcrypto, pgjwt, pgroonga, pgroonga_database, pgrouting, pgrowlocks, pgsodium, pgstattuple, pgtap, plcoffee, pljava, plls, plpgsql_check, postgis, postgis_raster, postgis_sfcgal, postgis_tiger_geocoder, postgis_topology, postgres_fdw, refint, rum, seg, sslinfo, supabase_vault, supautils, tablefunc, tcn, tsm_system_rows, tsm_system_time, unaccent, uuid-ossp, vector, wrappers"; + }; + }; + + systemd.services.postgresql-migrate = { + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + User = "postgres"; + Group = "postgres"; + StateDirectory = "postgresql"; + WorkingDirectory = "${builtins.dirOf config.services.postgresql.dataDir}"; + }; + script = + let + oldPostgresql = psql_15; + newPostgresql = psql_17; + oldDataDir = "${builtins.dirOf config.services.postgresql.dataDir}/${oldPostgresql.psqlSchema}"; + newDataDir = "${builtins.dirOf config.services.postgresql.dataDir}/${newPostgresql.psqlSchema}"; + in + '' + if [[ ! -d ${newDataDir} ]]; then + install -d -m 0700 -o postgres -g postgres "${newDataDir}" + ${newPostgresql}/bin/initdb -D "${newDataDir}" + ${newPostgresql}/bin/pg_upgrade --old-datadir "${oldDataDir}" --new-datadir "${newDataDir}" \ + --old-bindir "${oldPostgresql}/bin" --new-bindir "${newPostgresql}/bin" \ + --old-options='-c shared_preload_libraries=supautils' --new-options='-c shared_preload_libraries=supautils' + else + echo "${newDataDir} already exists" + fi + ''; + }; + + systemd.services.postgresql = { + after = [ "postgresql-migrate.service" ]; + requires = [ "postgresql-migrate.service" ]; + }; + }; + }; + testScript = + { nodes, ... }: + let + pg17-configuration = "${nodes.server.system.build.toplevel}/specialisation/postgresql17"; + in + '' + from pathlib import Path + versions = { + "15": [${lib.concatStringsSep ", " (map (s: ''"${s}"'') (versions "15"))}], + "17": [${lib.concatStringsSep ", " (map (s: ''"${s}"'') (versions "17"))}], + } + extension_name = "${pname}" + support_upgrade = False + pg17_configuration = "${pg17-configuration}" + ext_has_background_worker = ${ + if (installedExtension "15") ? hasBackgroundWorker then "True" else "False" + } + sql_test_directory = Path("${../../tests}") + pg_regress_test_name = "${(installedExtension "15").pgRegressTestName or pname}" + + ${builtins.readFile ./lib.py} + + start_all() + + server.wait_for_unit("multi-user.target") + server.wait_for_unit("postgresql.service") + + test = PostgresExtensionTest(server, extension_name, versions, sql_test_directory, support_upgrade) + + with subtest("Check pg_regress with postgresql 15 after extension upgrade"): + test.check_pg_regress(Path("${psql_15}/lib/pgxs/src/test/regress/pg_regress"), "15", pg_regress_test_name) + + with subtest("switch to postgresql 17"): + server.succeed( + f"{pg17_configuration}/bin/switch-to-configuration test >&2" + ) + + with subtest("Check pg_regress with postgresql 17 after extension upgrade"): + test.check_pg_regress(Path("${psql_17}/lib/pgxs/src/test/regress/pg_regress"), "17", pg_regress_test_name) + ''; +} diff --git a/nix/ext/versions.json b/nix/ext/versions.json index dc7ef8fd2..25930df56 100644 --- a/nix/ext/versions.json +++ b/nix/ext/versions.json @@ -507,6 +507,15 @@ "hash": "sha256-MC87bqgtynnDhmNZAu96jvfCpsGDCPB0g5TZfRQHd30=" } }, + "supautils": { + "3.0.1": { + "postgresql": [ + "15", + "17" + ], + "hash": "sha256-j0iASDzmcZRLbHaS9ZNRWwzii7mcC+8wYHM0/mOLkbs=" + } + }, "timescaledb": { "2.9.1": { "postgresql": [ diff --git a/nix/tests/expected/roles.out b/nix/tests/expected/roles.out index 9c5a47a71..2d9876119 100644 --- a/nix/tests/expected/roles.out +++ b/nix/tests/expected/roles.out @@ -59,11 +59,11 @@ select from pg_roles r where rolname not in ('pg_create_subscription', 'pg_maintain', 'pg_use_reserved_connections') order by rolname; - rolname | rolconfig -----------------------------+--------------------------------------------------------------------------------- + rolname | rolconfig +----------------------------+------------------------------------------------------------------------------------------ anon | {statement_timeout=3s} authenticated | {statement_timeout=8s} - authenticator | {session_preload_libraries=safeupdate,statement_timeout=8s,lock_timeout=8s} + authenticator | {"session_preload_libraries=supautils, safeupdate",statement_timeout=8s,lock_timeout=8s} dashboard_user | pg_checkpoint | pg_database_owner | diff --git a/nix/tests/expected/z_15_ext_interface.out b/nix/tests/expected/z_15_ext_interface.out index f2b5672aa..0577ee87c 100644 --- a/nix/tests/expected/z_15_ext_interface.out +++ b/nix/tests/expected/z_15_ext_interface.out @@ -31,9 +31,10 @@ order by ----------------- pg_cron pgjwt + supautils tsm_system_time wal2json -(4 rows) +(5 rows) /* diff --git a/nix/tests/expected/z_15_supautils.out b/nix/tests/expected/z_15_supautils.out new file mode 100644 index 000000000..cfbfdeba8 --- /dev/null +++ b/nix/tests/expected/z_15_supautils.out @@ -0,0 +1,39 @@ +begin; + load 'supautils'; + -- verify that supautils configuration parameters exist + select current_setting('supautils.privileged_extensions', true) is not null as has_privileged_extensions; + has_privileged_extensions +--------------------------- + t +(1 row) + + select current_setting('supautils.privileged_role', true) is not null as has_privileged_role; + has_privileged_role +--------------------- + t +(1 row) + + -- switch to postgres role and verify access to settings + set role postgres; + select current_setting('supautils.privileged_extensions', true) as privileged_extensions; + privileged_extensions +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + address_standardizer, address_standardizer_data_us, autoinc, bloom, btree_gin, btree_gist, citext, cube, dblink, dict_int, dict_xsyn, earthdistance, fuzzystrmatch, hstore, http, hypopg, index_advisor, insert_username, intarray, isn, ltree, moddatetime, orioledb, pg_buffercache, pg_cron, pg_graphql, pg_hashids, pg_jsonschema, pg_net, pg_prewarm, pg_repack, pg_stat_monitor, pg_stat_statements, pg_tle, pg_trgm, pg_walinspect, pgaudit, pgcrypto, pgjwt, pgroonga, pgroonga_database, pgrouting, pgrowlocks, pgsodium, pgstattuple, pgtap, plcoffee, pljava, plls, plpgsql_check, plv8, postgis, postgis_raster, postgis_sfcgal, postgis_tiger_geocoder, postgis_topology, postgres_fdw, refint, rum, seg, sslinfo, supabase_vault, supautils, tablefunc, tcn, timescaledb, tsm_system_rows, tsm_system_time, unaccent, uuid-ossp, vector, wrappers +(1 row) + + -- create a simple schema to verify normal operations work + create schema v; + create table v.test_table ( + id serial primary key, + data text + ); + insert into v.test_table (data) + values ('test1'), ('test2'); + select * from v.test_table order by id; + id | data +----+------- + 1 | test1 + 2 | test2 +(2 rows) + +rollback; diff --git a/nix/tests/expected/z_17_ext_interface.out b/nix/tests/expected/z_17_ext_interface.out index 57f68aa1c..357004f8e 100644 --- a/nix/tests/expected/z_17_ext_interface.out +++ b/nix/tests/expected/z_17_ext_interface.out @@ -25,9 +25,10 @@ order by pg_cron pgjwt postgis_tiger_geocoder + supautils tsm_system_time wal2json -(5 rows) +(6 rows) /* diff --git a/nix/tests/expected/z_17_supautils.out b/nix/tests/expected/z_17_supautils.out new file mode 100644 index 000000000..10fc68e19 --- /dev/null +++ b/nix/tests/expected/z_17_supautils.out @@ -0,0 +1,39 @@ +begin; + load 'supautils'; + -- verify that supautils configuration parameters exist + select current_setting('supautils.privileged_extensions', true) is not null as has_privileged_extensions; + has_privileged_extensions +--------------------------- + t +(1 row) + + select current_setting('supautils.privileged_role', true) is not null as has_privileged_role; + has_privileged_role +--------------------- + t +(1 row) + + -- switch to postgres role and verify access to settings + set role postgres; + select current_setting('supautils.privileged_extensions', true) as privileged_extensions; + privileged_extensions +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + address_standardizer, address_standardizer_data_us, autoinc, bloom, btree_gin, btree_gist, citext, cube, dblink, dict_int, dict_xsyn, earthdistance, fuzzystrmatch, hstore, http, hypopg, index_advisor, insert_username, intarray, isn, ltree, moddatetime, orioledb, pg_buffercache, pg_cron, pg_graphql, pg_hashids, pg_jsonschema, pg_net, pg_prewarm, pg_repack, pg_stat_monitor, pg_stat_statements, pg_tle, pg_trgm, pg_walinspect, pgaudit, pgcrypto, pgjwt, pgroonga, pgroonga_database, pgrouting, pgrowlocks, pgsodium, pgstattuple, pgtap, plcoffee, pljava, plls, plpgsql_check, postgis, postgis_raster, postgis_sfcgal, postgis_tiger_geocoder, postgis_topology, postgres_fdw, refint, rum, seg, sslinfo, supabase_vault, supautils, tablefunc, tcn, tsm_system_rows, tsm_system_time, unaccent, uuid-ossp, vector, wrappers +(1 row) + + -- create a simple schema to verify normal operations work + create schema v; + create table v.test_table ( + id serial primary key, + data text + ); + insert into v.test_table (data) + values ('test1'), ('test2'); + select * from v.test_table order by id; + id | data +----+------- + 1 | test1 + 2 | test2 +(2 rows) + +rollback; diff --git a/nix/tests/expected/z_orioledb-17_ext_interface.out b/nix/tests/expected/z_orioledb-17_ext_interface.out index 57f68aa1c..357004f8e 100644 --- a/nix/tests/expected/z_orioledb-17_ext_interface.out +++ b/nix/tests/expected/z_orioledb-17_ext_interface.out @@ -25,9 +25,10 @@ order by pg_cron pgjwt postgis_tiger_geocoder + supautils tsm_system_time wal2json -(5 rows) +(6 rows) /* diff --git a/nix/tests/sql/z_15_supautils.sql b/nix/tests/sql/z_15_supautils.sql new file mode 100644 index 000000000..35f50b602 --- /dev/null +++ b/nix/tests/sql/z_15_supautils.sql @@ -0,0 +1,25 @@ +begin; + load 'supautils'; + + -- verify that supautils configuration parameters exist + select current_setting('supautils.privileged_extensions', true) is not null as has_privileged_extensions; + select current_setting('supautils.privileged_role', true) is not null as has_privileged_role; + + -- switch to postgres role and verify access to settings + set role postgres; + select current_setting('supautils.privileged_extensions', true) as privileged_extensions; + + -- create a simple schema to verify normal operations work + create schema v; + + create table v.test_table ( + id serial primary key, + data text + ); + + insert into v.test_table (data) + values ('test1'), ('test2'); + + select * from v.test_table order by id; + +rollback; diff --git a/nix/tests/sql/z_17_supautils.sql b/nix/tests/sql/z_17_supautils.sql new file mode 100644 index 000000000..35f50b602 --- /dev/null +++ b/nix/tests/sql/z_17_supautils.sql @@ -0,0 +1,25 @@ +begin; + load 'supautils'; + + -- verify that supautils configuration parameters exist + select current_setting('supautils.privileged_extensions', true) is not null as has_privileged_extensions; + select current_setting('supautils.privileged_role', true) is not null as has_privileged_role; + + -- switch to postgres role and verify access to settings + set role postgres; + select current_setting('supautils.privileged_extensions', true) as privileged_extensions; + + -- create a simple schema to verify normal operations work + create schema v; + + create table v.test_table ( + id serial primary key, + data text + ); + + insert into v.test_table (data) + values ('test1'), ('test2'); + + select * from v.test_table order by id; + +rollback;