From c06be51db087cec31752ec3dbb3f320a5c7984f9 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 | 147 +++++++++++++++++++++++++++++++ nix/ext/versions.json | 9 ++ nix/tests/expected/supautils.out | 17 ++++ nix/tests/sql/supautils.sql | 12 +++ 5 files changed, 267 insertions(+), 19 deletions(-) create mode 100644 nix/ext/tests/supautils.nix create mode 100644 nix/tests/expected/supautils.out create mode 100644 nix/tests/sql/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..3f5d6c532 --- /dev/null +++ b/nix/ext/tests/supautils.nix @@ -0,0 +1,147 @@ +{ 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 = "supautils"; + hostPkgs = pkgs; + nodes.server = + { config, ... }: + { + services.postgresql = { + enable = true; + package = (postgresqlWithExtension psql_15); + authentication = '' + local all postgres peer map=postgres + local all all peer map=root + ''; + identMap = '' + root root supabase_admin + postgres postgres postgres + ''; + ensureUsers = [ + { + name = "supabase_admin"; + ensureClauses.superuser = true; + } + ]; + settings = (installedExtension "15").defaultSettings or { }; + }; + + specialisation.postgresql17.configuration = { + services.postgresql = { + package = lib.mkForce psql_17; + }; + + 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" + 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 = True + 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 15 after extension upgrade"): + test.check_pg_regress(Path("${psql_15}/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/supautils.out b/nix/tests/expected/supautils.out new file mode 100644 index 000000000..5ed7d45df --- /dev/null +++ b/nix/tests/expected/supautils.out @@ -0,0 +1,17 @@ +select current_setting('supautils.privileged_role', false); + current_setting +----------------- + postgres +(1 row) + +select current_setting('supautils.privileged_extensions_superuser', false); + current_setting +----------------- + supabase_admin +(1 row) + +-- superuser can't execute COPY ... PROGRAM +copy (select '') to program 'id'; +ERROR: COPY TO/FROM PROGRAM not allowed +DETAIL: The copy to/from program utility statement is disabled +\echo diff --git a/nix/tests/sql/supautils.sql b/nix/tests/sql/supautils.sql new file mode 100644 index 000000000..4f858fb13 --- /dev/null +++ b/nix/tests/sql/supautils.sql @@ -0,0 +1,12 @@ +-- create a superuser role +create role super2 superuser; +set role super2; +\echo + +LOAD 'supautils'; + +select current_setting('supautils.privileged_role', false); + +-- superuser can't execute COPY ... PROGRAM +copy (select '') to program 'id'; +\echo