diff --git a/.github/workflows/check-extension-versions.yml b/.github/workflows/check-extension-versions.yml new file mode 100644 index 0000000000..18888a57c8 --- /dev/null +++ b/.github/workflows/check-extension-versions.yml @@ -0,0 +1,37 @@ +name: Check Extension Versions + +on: + workflow_dispatch: + schedule: + - cron: '0 6 * * 1' + +jobs: + check-extension-versions: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Checkout repository + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + - name: Install Nix + uses: ./.github/actions/nix-install-ephemeral + + - name: Check for new extension versions + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: python3 nix/tools/check-ext-versions.py + + - name: Create Pull Request + uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: "chore: update extension versions" + title: "chore: update extension versions" + body: Automated weekly check of `nix/ext/versions.json` against upstream GitHub tags. + branch: auto-update-extension-versions + base: develop + labels: | + dependencies + automated diff --git a/nix/ext/hypopg.nix b/nix/ext/hypopg.nix index 9bd4927263..4405e87c74 100644 --- a/nix/ext/hypopg.nix +++ b/nix/ext/hypopg.nix @@ -98,6 +98,7 @@ buildEnv { ''; passthru = { + github = "HypoPG/hypopg"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/index_advisor.nix b/nix/ext/index_advisor.nix index 5892127142..f9fee5aed2 100644 --- a/nix/ext/index_advisor.nix +++ b/nix/ext/index_advisor.nix @@ -83,6 +83,7 @@ pkgs.buildEnv { ]; passthru = { + github = "supabase/index_advisor"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/pg-safeupdate.nix b/nix/ext/pg-safeupdate.nix index 452e9c2c5e..869a7199a7 100644 --- a/nix/ext/pg-safeupdate.nix +++ b/nix/ext/pg-safeupdate.nix @@ -83,6 +83,7 @@ pkgs.buildEnv { ''; passthru = { + github = "eradman/pg-safeupdate"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/pg_cron/default.nix b/nix/ext/pg_cron/default.nix index 197420c982..965f7282fe 100644 --- a/nix/ext/pg_cron/default.nix +++ b/nix/ext/pg_cron/default.nix @@ -139,6 +139,7 @@ buildEnv { }; passthru = { + github = "citusdata/pg_cron"; perVersion = lib.mapAttrs (name: value: build name value) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/pg_graphql/default.nix b/nix/ext/pg_graphql/default.nix index cbab08aa76..3a595a249b 100644 --- a/nix/ext/pg_graphql/default.nix +++ b/nix/ext/pg_graphql/default.nix @@ -184,6 +184,7 @@ in --prefix EXT_WRAPPER : "$out" --prefix EXT_NAME : "${pname}" ''; passthru = { + github = "supabase/pg_graphql"; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/pg_hashids.nix b/nix/ext/pg_hashids.nix index d9d4a34077..3c5d0da5aa 100644 --- a/nix/ext/pg_hashids.nix +++ b/nix/ext/pg_hashids.nix @@ -106,6 +106,7 @@ buildEnv { ''; passthru = { + github = "iCyberon/pg_hashids"; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/pg_jsonschema/default.nix b/nix/ext/pg_jsonschema/default.nix index ec39a69fe2..848b507665 100644 --- a/nix/ext/pg_jsonschema/default.nix +++ b/nix/ext/pg_jsonschema/default.nix @@ -184,6 +184,7 @@ in ''; passthru = { + github = "supabase/pg_jsonschema"; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/pg_net.nix b/nix/ext/pg_net.nix index f769bad314..cb9e475512 100644 --- a/nix/ext/pg_net.nix +++ b/nix/ext/pg_net.nix @@ -145,6 +145,7 @@ pkgs.buildEnv { ''; passthru = { + github = "supabase/pg_net"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/pg_partman.nix b/nix/ext/pg_partman.nix index 0c8a4eee6e..5eb5c7b6ea 100644 --- a/nix/ext/pg_partman.nix +++ b/nix/ext/pg_partman.nix @@ -100,6 +100,7 @@ pkgs.buildEnv { ''; passthru = { + github = "pgpartman/pg_partman"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/pg_plan_filter.nix b/nix/ext/pg_plan_filter.nix index 847c7a2d11..237b7b63e3 100644 --- a/nix/ext/pg_plan_filter.nix +++ b/nix/ext/pg_plan_filter.nix @@ -86,6 +86,7 @@ pkgs.buildEnv { ''; passthru = { + github = "pgexperts/pg_plan_filter"; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/pg_repack.nix b/nix/ext/pg_repack.nix index 78d9764063..c7c1ea6459 100644 --- a/nix/ext/pg_repack.nix +++ b/nix/ext/pg_repack.nix @@ -140,6 +140,7 @@ buildEnv { ''; passthru = { + github = "reorg/pg_repack"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/pg_stat_monitor.nix b/nix/ext/pg_stat_monitor.nix index 9a58464a66..89f55c2b38 100644 --- a/nix/ext/pg_stat_monitor.nix +++ b/nix/ext/pg_stat_monitor.nix @@ -110,6 +110,7 @@ buildEnv { ''; passthru = { + github = "percona/pg_stat_monitor"; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/pg_tle.nix b/nix/ext/pg_tle.nix index 9c7ef86755..e12756bc58 100644 --- a/nix/ext/pg_tle.nix +++ b/nix/ext/pg_tle.nix @@ -111,6 +111,7 @@ buildEnv { ''; passthru = { + github = "aws/pg_tle"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/pgaudit.nix b/nix/ext/pgaudit.nix index 8fb727c666..f58ba8f4df 100644 --- a/nix/ext/pgaudit.nix +++ b/nix/ext/pgaudit.nix @@ -240,6 +240,7 @@ buildEnv { ''; passthru = { + github = "pgaudit/pgaudit"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/pgjwt.nix b/nix/ext/pgjwt.nix index 348b534c34..c9df64851b 100644 --- a/nix/ext/pgjwt.nix +++ b/nix/ext/pgjwt.nix @@ -82,6 +82,7 @@ buildEnv { pathsToLink = [ "/share/postgresql/extension" ]; passthru = { + github = "michelp/pgjwt"; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/pgmq/default.nix b/nix/ext/pgmq/default.nix index 7e2076a2f5..cc604f24a6 100644 --- a/nix/ext/pgmq/default.nix +++ b/nix/ext/pgmq/default.nix @@ -103,6 +103,7 @@ buildEnv { pathsToLink = [ "/share/postgresql/extension" ]; passthru = { + github = "pgmq/pgmq"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/pgroonga/default.nix b/nix/ext/pgroonga/default.nix index b9c3829a0d..71e9f49c64 100644 --- a/nix/ext/pgroonga/default.nix +++ b/nix/ext/pgroonga/default.nix @@ -181,6 +181,7 @@ buildEnv { ''; passthru = { + github = "pgroonga/pgroonga"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/pgrouting/default.nix b/nix/ext/pgrouting/default.nix index cc00281e70..79f3af8f89 100644 --- a/nix/ext/pgrouting/default.nix +++ b/nix/ext/pgrouting/default.nix @@ -153,6 +153,7 @@ buildEnv { ''; passthru = { + github = "pgRouting/pgrouting"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/pgsodium.nix b/nix/ext/pgsodium.nix index b5a2dcb72c..0e619e8cee 100644 --- a/nix/ext/pgsodium.nix +++ b/nix/ext/pgsodium.nix @@ -112,6 +112,7 @@ pkgs.buildEnv { ''; passthru = { + github = "michelp/pgsodium"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/pgsql-http.nix b/nix/ext/pgsql-http.nix index 885fcc472e..1982885ddc 100644 --- a/nix/ext/pgsql-http.nix +++ b/nix/ext/pgsql-http.nix @@ -113,6 +113,7 @@ pkgs.buildEnv { ''; passthru = { + github = "pramsey/pgsql-http"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/pgtap.nix b/nix/ext/pgtap.nix index f283774dc8..37697ea861 100644 --- a/nix/ext/pgtap.nix +++ b/nix/ext/pgtap.nix @@ -134,6 +134,7 @@ buildEnv { ''; passthru = { + github = "theory/pgtap"; inherit versions numberOfVersions; pname = "${pname}-all"; version = diff --git a/nix/ext/pgvector.nix b/nix/ext/pgvector.nix index 2db9d0c123..084cbaa22b 100644 --- a/nix/ext/pgvector.nix +++ b/nix/ext/pgvector.nix @@ -96,6 +96,7 @@ pkgs.buildEnv { ''; passthru = { + github = "pgvector/pgvector"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/plpgsql-check.nix b/nix/ext/plpgsql-check.nix index a9901eb0a5..ca6bd4de3b 100644 --- a/nix/ext/plpgsql-check.nix +++ b/nix/ext/plpgsql-check.nix @@ -139,6 +139,7 @@ buildEnv { ''; passthru = { + github = "okbob/plpgsql_check"; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit switch-ext-version latestOnly; diff --git a/nix/ext/plv8/default.nix b/nix/ext/plv8/default.nix index 731991937c..e25f227125 100644 --- a/nix/ext/plv8/default.nix +++ b/nix/ext/plv8/default.nix @@ -238,6 +238,7 @@ buildEnv { ''; passthru = { + github = "plv8/plv8"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/postgis.nix b/nix/ext/postgis.nix index 3f722d9ad9..9ff62ce78c 100644 --- a/nix/ext/postgis.nix +++ b/nix/ext/postgis.nix @@ -256,6 +256,7 @@ in ''; passthru = { + github = "postgis/postgis"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/rum.nix b/nix/ext/rum.nix index 31e6bae07b..49b13e5953 100644 --- a/nix/ext/rum.nix +++ b/nix/ext/rum.nix @@ -107,6 +107,7 @@ buildEnv { ''; passthru = { + github = "postgrespro/rum"; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/timescaledb.nix b/nix/ext/timescaledb.nix index dbfb2a8365..59f9cda7ec 100644 --- a/nix/ext/timescaledb.nix +++ b/nix/ext/timescaledb.nix @@ -152,6 +152,7 @@ buildEnv { ]; passthru = { + github = "timescale/timescaledb"; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit switch-ext-version latestOnly; diff --git a/nix/ext/vault.nix b/nix/ext/vault.nix index 9ab5391b1d..ea26be5e3b 100644 --- a/nix/ext/vault.nix +++ b/nix/ext/vault.nix @@ -99,6 +99,7 @@ pkgs.buildEnv { ''; passthru = { + github = "supabase/vault"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/wal2json.nix b/nix/ext/wal2json.nix index b082301c82..38fc2394e0 100644 --- a/nix/ext/wal2json.nix +++ b/nix/ext/wal2json.nix @@ -106,6 +106,7 @@ pkgs.buildEnv { ''; passthru = { + github = "eulerto/wal2json"; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/wrappers/default.nix b/nix/ext/wrappers/default.nix index 3ae64afbd1..2937a53428 100644 --- a/nix/ext/wrappers/default.nix +++ b/nix/ext/wrappers/default.nix @@ -366,6 +366,7 @@ in } ''; passthru = { + github = "supabase/wrappers"; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; pname = "${pname}"; diff --git a/nix/tools/check-ext-versions.py b/nix/tools/check-ext-versions.py new file mode 100755 index 0000000000..516d498f43 --- /dev/null +++ b/nix/tools/check-ext-versions.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +"""Check nix/ext/versions.json extensions against upstream GitHub tags.""" + +import json +import os +import re +import subprocess +import sys + +REPO_ROOT = subprocess.run( + ["git", "rev-parse", "--show-toplevel"], capture_output=True, text=True, check=True +).stdout.strip() +VERSIONS_FILE = os.path.join(REPO_ROOT, "nix/ext/versions.json") + +# `exts` attribute name -> versions.json catalog key, where they differ. +ATTR_TO_CATALOG_KEY = {"plan_filter": "pg_plan_filter"} + +# Extensions where we don't attempt a hash bump: fetchurl-based source, or a +# cargoHash/pgrx vendor hash that needs an actual build to compute. Version +# gets bumped anyway with a placeholder hash for a human to fill in. +NO_HASH_EXTS = {"pg_graphql", "wrappers", "postgis", "pgroonga"} + +CLEAN_TAG_RE = re.compile(r"^(?:v|ver_)?(\d+(?:\.\d+){0,3})$") +LEADING_VERSION_RE = re.compile(r"^(\d+(?:\.\d+)*)") + + +def parse_version(s: str) -> tuple[int, ...]: + m = LEADING_VERSION_RE.match(s) + if not m: + return (0,) + return tuple(int(p) for p in m.group(1).split(".")) + + +def best_candidate(tags: list[str], repo: str) -> tuple[tuple[int, ...], str] | None: + # repo-prefixed underscore tags (wal2json_2_6) - only trusted when the + # prefix is the actual repo name, not any legacy tag scheme (REL0_9_1). + prefixed_re = re.compile( + rf"^{re.escape(repo)}[-_](\d+(?:[._]\d+){{1,3}})$", re.IGNORECASE + ) + candidates: list[tuple[tuple[int, ...], str]] = [] + for tag in tags: + m = CLEAN_TAG_RE.match(tag) or prefixed_re.match(tag) + if not m: + continue + version_str = m.group(1).replace("_", ".") + candidates.append((parse_version(version_str), tag)) + return max(candidates, default=None) + + +def github_metadata(system: str) -> dict[str, str]: + expr = ( + "exts: builtins.listToAttrs (map (n: { name = n; value = exts.${n}.github; })" + " (builtins.filter (n: exts.${n} ? github) (builtins.attrNames exts)))" + ) + out = subprocess.run( + [ + "nix", + "eval", + "--json", + f".#legacyPackages.{system}.psql_15.exts", + "--apply", + expr, + ], + cwd=REPO_ROOT, + capture_output=True, + text=True, + check=True, + ).stdout + return json.loads(out) + + +def fetch_tags(owner: str, repo: str) -> list[str] | None: + result = subprocess.run( + ["gh", "api", f"repos/{owner}/{repo}/tags", "--paginate"], + capture_output=True, + text=True, + ) + if result.returncode != 0: + return None + return [t["name"] for t in json.loads(result.stdout)] + + +def prefetch_hash(owner: str, repo: str, tag: str) -> str | None: + url = f"https://github.com/{owner}/{repo}/archive/{tag}.tar.gz" + prefetch = subprocess.run( + ["nix-prefetch-url", "--type", "sha256", "--unpack", url], + capture_output=True, + text=True, + ) + if prefetch.returncode != 0: + return None + sha256 = prefetch.stdout.strip().splitlines()[-1] + sri = subprocess.run( + ["nix", "hash", "to-sri", "--type", "sha256", sha256], + capture_output=True, + text=True, + check=True, + ) + return sri.stdout.strip() + + +def main() -> None: + system = subprocess.run( + ["nix", "eval", "--impure", "--raw", "--expr", "builtins.currentSystem"], + capture_output=True, + text=True, + check=True, + ).stdout.strip() + + with open(VERSIONS_FILE) as f: + versions = json.load(f) + + changed = False + for attr, repo_slug in github_metadata(system).items(): + ext = ATTR_TO_CATALOG_KEY.get(attr, attr) + if ext not in versions: + continue + owner, repo = repo_slug.split("/", 1) + + tags = fetch_tags(owner, repo) + if tags is None: + print(f"skip {ext}: tags lookup failed") + continue + + candidate = best_candidate(tags, repo) + if candidate is None: + print(f"skip {ext}: no clean version tags") + continue + candidate_version, tag = candidate + + entries = versions[ext] + current_key = max(entries, key=parse_version) + if candidate_version <= parse_version(current_key): + continue + + if ext in NO_HASH_EXTS: + sri_hash = "" + else: + sri_hash = prefetch_hash(owner, repo, tag) + if sri_hash is None: + print(f"skip {ext}: prefetch failed for {tag}") + continue + + version_str = ".".join(str(p) for p in candidate_version) + entries[version_str] = { + "postgresql": entries[current_key]["postgresql"], + "revision": tag, + "rev": tag, + "hash": sri_hash, + } + changed = True + suffix = " [no hash, needs manual fill-in]" if not sri_hash else "" + print(f"updated {ext} -> {version_str} ({tag}){suffix}") + + if changed: + with open(VERSIONS_FILE, "w") as f: + json.dump(versions, f, indent=2) + f.write("\n") + + github_output = os.environ.get("GITHUB_OUTPUT") + if github_output: + with open(github_output, "a") as f: + f.write(f"changed={'true' if changed else 'false'}\n") + + +if __name__ == "__main__": + sys.exit(main())