Skip to content

Commit d6ffec1

Browse files
committed
feat: support multiple versions of the supautils extension
1 parent 1d4af35 commit d6ffec1

File tree

11 files changed

+367
-26
lines changed

11 files changed

+367
-26
lines changed

nix/ext/supautils.nix

Lines changed: 81 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,96 @@
11
{
2+
pkgs,
23
lib,
34
stdenv,
45
fetchFromGitHub,
56
postgresql,
67
}:
7-
8-
stdenv.mkDerivation rec {
8+
let
99
pname = "supautils";
10-
name = pname;
11-
version = "3.0.1";
10+
# Load version configuration from external file
11+
allVersions = (builtins.fromJSON (builtins.readFile ./versions.json)).${pname};
1212

13-
buildInputs = [ postgresql ];
13+
# Filter versions compatible with current PostgreSQL version
14+
supportedVersions = lib.filterAttrs (
15+
_: value: builtins.elem (lib.versions.major postgresql.version) value.postgresql
16+
) allVersions;
1417

15-
src = fetchFromGitHub {
16-
owner = "supabase";
17-
repo = pname;
18-
rev = "refs/tags/v${version}";
19-
hash = "sha256-j0iASDzmcZRLbHaS9ZNRWwzii7mcC+8wYHM0/mOLkbs=";
20-
};
18+
# Derived version information
19+
versions = lib.naturalSort (lib.attrNames supportedVersions);
20+
latestVersion = lib.last versions;
21+
numberOfVersions = builtins.length versions;
22+
packages = builtins.attrValues (
23+
lib.mapAttrs (name: value: build name value.hash) supportedVersions
24+
);
25+
26+
# Build function for individual versions
27+
build =
28+
version: hash:
29+
stdenv.mkDerivation rec {
30+
inherit pname version;
31+
32+
buildInputs = [ postgresql ];
33+
34+
src = fetchFromGitHub {
35+
owner = "supabase";
36+
repo = pname;
37+
rev = "refs/tags/v${version}";
38+
inherit hash;
39+
};
2140

22-
installPhase = ''
23-
mkdir -p $out/lib
41+
installPhase = ''
42+
runHook preInstall
43+
44+
mkdir -p $out/{lib,share/postgresql/extension}
45+
46+
# Install shared library with version suffix
47+
mv ${pname}${postgresql.dlSuffix} $out/lib/${pname}-${version}${postgresql.dlSuffix}
48+
49+
# Create version-specific control file
50+
cat <<EOF > $out/share/postgresql/extension/${pname}--${version}.control
51+
module_pathname = '$libdir/supautils'
52+
relocatable = false
53+
EOF
54+
55+
runHook postInstall
56+
'';
57+
58+
meta = with lib; {
59+
description = "PostgreSQL extension for enhanced security";
60+
homepage = "https://github.com/supabase/${pname}";
61+
maintainers = with maintainers; [ steve-chavez ];
62+
inherit (postgresql.meta) platforms;
63+
license = licenses.postgresql;
64+
};
65+
};
66+
in
67+
pkgs.buildEnv {
68+
name = pname;
69+
paths = packages;
70+
pathsToLink = [
71+
"/lib"
72+
"/share/postgresql/extension"
73+
];
74+
postBuild = ''
75+
# Create symlinks to latest version for library and control file
76+
ln -sfn ${pname}-${latestVersion}${postgresql.dlSuffix} $out/lib/${pname}${postgresql.dlSuffix}
2477
25-
install -D *${postgresql.dlSuffix} -t $out/lib
78+
# Create default control file pointing to latest
79+
{
80+
echo "default_version = '${latestVersion}'"
81+
cat $out/share/postgresql/extension/${pname}--${latestVersion}.control
82+
} > $out/share/postgresql/extension/${pname}.control
2683
'';
2784

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

nix/ext/tests/supautils.nix

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

nix/ext/versions.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,15 @@
517517
"hash": "sha256-MC87bqgtynnDhmNZAu96jvfCpsGDCPB0g5TZfRQHd30="
518518
}
519519
},
520+
"supautils": {
521+
"3.0.1": {
522+
"postgresql": [
523+
"15",
524+
"17"
525+
],
526+
"hash": "sha256-j0iASDzmcZRLbHaS9ZNRWwzii7mcC+8wYHM0/mOLkbs="
527+
}
528+
},
520529
"timescaledb": {
521530
"2.9.1": {
522531
"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)