Skip to content

Commit 848d4b2

Browse files
committed
feat: support multiple versions of the supautils extension
1 parent 7f6e525 commit 848d4b2

File tree

11 files changed

+367
-25
lines changed

11 files changed

+367
-25
lines changed

nix/ext/supautils.nix

Lines changed: 82 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,97 @@
11
{
2+
pkgs,
23
lib,
34
stdenv,
45
fetchFromGitHub,
56
postgresql,
67
}:
7-
8-
stdenv.mkDerivation rec {
8+
let
99
pname = "supautils";
10-
version = "3.0.1";
1110

12-
buildInputs = [ postgresql ];
11+
# Load version configuration from external file
12+
allVersions = (builtins.fromJSON (builtins.readFile ./versions.json)).${pname};
1313

14-
src = fetchFromGitHub {
15-
owner = "supabase";
16-
repo = pname;
17-
rev = "refs/tags/v${version}";
18-
hash = "sha256-j0iASDzmcZRLbHaS9ZNRWwzii7mcC+8wYHM0/mOLkbs=";
19-
};
14+
# Filter versions compatible with current PostgreSQL version
15+
supportedVersions = lib.filterAttrs (
16+
_: value: builtins.elem (lib.versions.major postgresql.version) value.postgresql
17+
) allVersions;
18+
19+
# Derived version information
20+
versions = lib.naturalSort (lib.attrNames supportedVersions);
21+
latestVersion = lib.last versions;
22+
numberOfVersions = builtins.length versions;
23+
packages = builtins.attrValues (
24+
lib.mapAttrs (name: value: build name value.hash) supportedVersions
25+
);
26+
27+
# Build function for individual versions
28+
build =
29+
version: hash:
30+
stdenv.mkDerivation rec {
31+
inherit pname version;
32+
33+
buildInputs = [ postgresql ];
34+
35+
src = fetchFromGitHub {
36+
owner = "supabase";
37+
repo = pname;
38+
rev = "refs/tags/v${version}";
39+
inherit hash;
40+
};
41+
42+
installPhase = ''
43+
runHook preInstall
44+
45+
mkdir -p $out/{lib,share/postgresql/extension}
46+
47+
# Install shared library with version suffix
48+
mv ${pname}${postgresql.dlSuffix} $out/lib/${pname}-${version}${postgresql.dlSuffix}
49+
50+
# Create version-specific control file
51+
cat <<EOF > $out/share/postgresql/extension/${pname}--${version}.control
52+
module_pathname = '$libdir/supautils'
53+
relocatable = false
54+
EOF
55+
56+
runHook postInstall
57+
'';
2058

21-
installPhase = ''
22-
mkdir -p $out/lib
59+
meta = with lib; {
60+
description = "PostgreSQL extension for enhanced security";
61+
homepage = "https://github.com/supabase/${pname}";
62+
maintainers = with maintainers; [ steve-chavez ];
63+
platforms = postgresql.meta.platforms;
64+
license = licenses.postgresql;
65+
};
66+
};
67+
in
68+
pkgs.buildEnv {
69+
name = pname;
70+
paths = packages;
71+
pathsToLink = [
72+
"/lib"
73+
"/share/postgresql/extension"
74+
];
75+
postBuild = ''
76+
# Create symlinks to latest version for library and control file
77+
ln -sfn ${pname}-${latestVersion}${postgresql.dlSuffix} $out/lib/${pname}${postgresql.dlSuffix}
2378
24-
install -D *${postgresql.dlSuffix} -t $out/lib
79+
# Create default control file pointing to latest
80+
{
81+
echo "default_version = '${latestVersion}'"
82+
cat $out/share/postgresql/extension/${pname}--${latestVersion}.control
83+
} > $out/share/postgresql/extension/${pname}.control
2584
'';
2685

27-
meta = with lib; {
28-
description = "PostgreSQL extension for enhanced security";
29-
homepage = "https://github.com/supabase/${pname}";
30-
maintainers = with maintainers; [ steve-chavez ];
31-
platforms = postgresql.meta.platforms;
32-
license = licenses.postgresql;
86+
passthru = {
87+
inherit versions numberOfVersions;
88+
pname = "${pname}-all";
89+
defaultSettings = {
90+
session_preload_libraries = "supautils";
91+
"supautils.disable_program" = "true";
92+
"supautils.privileged_role" = "privileged_role";
93+
};
94+
version =
95+
"multi-" + lib.concatStringsSep "-" (map (v: lib.replaceStrings [ "." ] [ "-" ] v) versions);
3396
};
3497
}

nix/ext/tests/supautils.nix

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
{ self, pkgs }:
2+
let
3+
pname = "supautils";
4+
inherit (pkgs) lib;
5+
installedExtension =
6+
postgresMajorVersion: self.packages.${pkgs.system}."psql_${postgresMajorVersion}/exts/${pname}-all";
7+
versions = postgresqlMajorVersion: (installedExtension postgresqlMajorVersion).versions;
8+
postgresqlWithExtension =
9+
postgresql:
10+
let
11+
majorVersion = lib.versions.major postgresql.version;
12+
pkg = pkgs.buildEnv {
13+
name = "postgresql-${majorVersion}-${pname}";
14+
paths = [
15+
postgresql
16+
postgresql.lib
17+
(installedExtension majorVersion)
18+
];
19+
passthru = {
20+
inherit (postgresql) version psqlSchema;
21+
lib = pkg;
22+
withPackages = _: pkg;
23+
};
24+
nativeBuildInputs = [ pkgs.makeWrapper ];
25+
pathsToLink = [
26+
"/"
27+
"/bin"
28+
"/lib"
29+
];
30+
postBuild = ''
31+
wrapProgram $out/bin/postgres --set NIX_PGLIBDIR $out/lib
32+
wrapProgram $out/bin/pg_ctl --set NIX_PGLIBDIR $out/lib
33+
wrapProgram $out/bin/pg_upgrade --set NIX_PGLIBDIR $out/lib
34+
'';
35+
};
36+
in
37+
pkg;
38+
psql_15 = postgresqlWithExtension self.packages.${pkgs.system}.postgresql_15;
39+
psql_17 = postgresqlWithExtension self.packages.${pkgs.system}.postgresql_17;
40+
in
41+
self.inputs.nixpkgs.lib.nixos.runTest {
42+
name = pname;
43+
hostPkgs = pkgs;
44+
nodes.server =
45+
{ config, ... }:
46+
{
47+
services.postgresql = {
48+
enable = true;
49+
package = (postgresqlWithExtension psql_15);
50+
settings = {
51+
shared_preload_libraries = "supautils";
52+
"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";
53+
};
54+
};
55+
56+
specialisation.postgresql17.configuration = {
57+
services.postgresql = {
58+
package = lib.mkForce psql_17;
59+
settings = {
60+
"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";
61+
};
62+
};
63+
64+
systemd.services.postgresql-migrate = {
65+
serviceConfig = {
66+
Type = "oneshot";
67+
RemainAfterExit = true;
68+
User = "postgres";
69+
Group = "postgres";
70+
StateDirectory = "postgresql";
71+
WorkingDirectory = "${builtins.dirOf config.services.postgresql.dataDir}";
72+
};
73+
script =
74+
let
75+
oldPostgresql = psql_15;
76+
newPostgresql = psql_17;
77+
oldDataDir = "${builtins.dirOf config.services.postgresql.dataDir}/${oldPostgresql.psqlSchema}";
78+
newDataDir = "${builtins.dirOf config.services.postgresql.dataDir}/${newPostgresql.psqlSchema}";
79+
in
80+
''
81+
if [[ ! -d ${newDataDir} ]]; then
82+
install -d -m 0700 -o postgres -g postgres "${newDataDir}"
83+
${newPostgresql}/bin/initdb -D "${newDataDir}"
84+
${newPostgresql}/bin/pg_upgrade --old-datadir "${oldDataDir}" --new-datadir "${newDataDir}" \
85+
--old-bindir "${oldPostgresql}/bin" --new-bindir "${newPostgresql}/bin" \
86+
--old-options='-c shared_preload_libraries=supautils' --new-options='-c shared_preload_libraries=supautils'
87+
else
88+
echo "${newDataDir} already exists"
89+
fi
90+
'';
91+
};
92+
93+
systemd.services.postgresql = {
94+
after = [ "postgresql-migrate.service" ];
95+
requires = [ "postgresql-migrate.service" ];
96+
};
97+
};
98+
};
99+
testScript =
100+
{ nodes, ... }:
101+
let
102+
pg17-configuration = "${nodes.server.system.build.toplevel}/specialisation/postgresql17";
103+
in
104+
''
105+
from pathlib import Path
106+
versions = {
107+
"15": [${lib.concatStringsSep ", " (map (s: ''"${s}"'') (versions "15"))}],
108+
"17": [${lib.concatStringsSep ", " (map (s: ''"${s}"'') (versions "17"))}],
109+
}
110+
extension_name = "${pname}"
111+
support_upgrade = False
112+
pg17_configuration = "${pg17-configuration}"
113+
ext_has_background_worker = ${
114+
if (installedExtension "15") ? hasBackgroundWorker then "True" else "False"
115+
}
116+
sql_test_directory = Path("${../../tests}")
117+
pg_regress_test_name = "${(installedExtension "15").pgRegressTestName or pname}"
118+
119+
${builtins.readFile ./lib.py}
120+
121+
start_all()
122+
123+
server.wait_for_unit("multi-user.target")
124+
server.wait_for_unit("postgresql.service")
125+
126+
test = PostgresExtensionTest(server, extension_name, versions, sql_test_directory, support_upgrade)
127+
128+
with subtest("Check pg_regress with postgresql 15 after extension upgrade"):
129+
test.check_pg_regress(Path("${psql_15}/lib/pgxs/src/test/regress/pg_regress"), "15", pg_regress_test_name)
130+
131+
with subtest("switch to postgresql 17"):
132+
server.succeed(
133+
f"{pg17_configuration}/bin/switch-to-configuration test >&2"
134+
)
135+
136+
with subtest("Check pg_regress with postgresql 17 after extension upgrade"):
137+
test.check_pg_regress(Path("${psql_17}/lib/pgxs/src/test/regress/pg_regress"), "17", pg_regress_test_name)
138+
'';
139+
}

nix/ext/versions.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,15 @@
507507
"hash": "sha256-MC87bqgtynnDhmNZAu96jvfCpsGDCPB0g5TZfRQHd30="
508508
}
509509
},
510+
"supautils": {
511+
"3.0.1": {
512+
"postgresql": [
513+
"15",
514+
"17"
515+
],
516+
"hash": "sha256-j0iASDzmcZRLbHaS9ZNRWwzii7mcC+8wYHM0/mOLkbs="
517+
}
518+
},
510519
"timescaledb": {
511520
"2.9.1": {
512521
"postgresql": [

nix/tests/expected/roles.out

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,11 @@ select
5959
from pg_roles r
6060
where rolname not in ('pg_create_subscription', 'pg_maintain', 'pg_use_reserved_connections')
6161
order by rolname;
62-
rolname | rolconfig
63-
----------------------------+---------------------------------------------------------------------------------
62+
rolname | rolconfig
63+
----------------------------+------------------------------------------------------------------------------------------
6464
anon | {statement_timeout=3s}
6565
authenticated | {statement_timeout=8s}
66-
authenticator | {session_preload_libraries=safeupdate,statement_timeout=8s,lock_timeout=8s}
66+
authenticator | {"session_preload_libraries=supautils, safeupdate",statement_timeout=8s,lock_timeout=8s}
6767
dashboard_user |
6868
pg_checkpoint |
6969
pg_database_owner |

nix/tests/expected/z_15_ext_interface.out

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,10 @@ order by
3131
-----------------
3232
pg_cron
3333
pgjwt
34+
supautils
3435
tsm_system_time
3536
wal2json
36-
(4 rows)
37+
(5 rows)
3738

3839
/*
3940

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
begin;
2+
load 'supautils';
3+
-- verify that supautils configuration parameters exist
4+
select current_setting('supautils.privileged_extensions', true) is not null as has_privileged_extensions;
5+
has_privileged_extensions
6+
---------------------------
7+
t
8+
(1 row)
9+
10+
select current_setting('supautils.privileged_role', true) is not null as has_privileged_role;
11+
has_privileged_role
12+
---------------------
13+
t
14+
(1 row)
15+
16+
-- switch to postgres role and verify access to settings
17+
set role postgres;
18+
select current_setting('supautils.privileged_extensions', true) as privileged_extensions;
19+
privileged_extensions
20+
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
21+
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
22+
(1 row)
23+
24+
-- create a simple schema to verify normal operations work
25+
create schema v;
26+
create table v.test_table (
27+
id serial primary key,
28+
data text
29+
);
30+
insert into v.test_table (data)
31+
values ('test1'), ('test2');
32+
select * from v.test_table order by id;
33+
id | data
34+
----+-------
35+
1 | test1
36+
2 | test2
37+
(2 rows)
38+
39+
rollback;

nix/tests/expected/z_17_ext_interface.out

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ order by
2525
pg_cron
2626
pgjwt
2727
postgis_tiger_geocoder
28+
supautils
2829
tsm_system_time
2930
wal2json
30-
(5 rows)
31+
(6 rows)
3132

3233
/*
3334

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
begin;
2+
load 'supautils';
3+
-- verify that supautils configuration parameters exist
4+
select current_setting('supautils.privileged_extensions', true) is not null as has_privileged_extensions;
5+
has_privileged_extensions
6+
---------------------------
7+
t
8+
(1 row)
9+
10+
select current_setting('supautils.privileged_role', true) is not null as has_privileged_role;
11+
has_privileged_role
12+
---------------------
13+
t
14+
(1 row)
15+
16+
-- switch to postgres role and verify access to settings
17+
set role postgres;
18+
select current_setting('supautils.privileged_extensions', true) as privileged_extensions;
19+
privileged_extensions
20+
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
21+
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
22+
(1 row)
23+
24+
-- create a simple schema to verify normal operations work
25+
create schema v;
26+
create table v.test_table (
27+
id serial primary key,
28+
data text
29+
);
30+
insert into v.test_table (data)
31+
values ('test1'), ('test2');
32+
select * from v.test_table order by id;
33+
id | data
34+
----+-------
35+
1 | test1
36+
2 | test2
37+
(2 rows)
38+
39+
rollback;

0 commit comments

Comments
 (0)