diff --git a/.github/workflows/check-extension-versions.yml b/.github/workflows/check-extension-versions.yml new file mode 100644 index 0000000000..485b4d4706 --- /dev/null +++ b/.github/workflows/check-extension-versions.yml @@ -0,0 +1,42 @@ +name: Check Extension Updates + +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: nix run .#check-ext-versions + + - 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: | + DO NOT MERGE + + Automated weekly check of `nix/ext/versions.json` against upstream GitHub tags. + draft: true + branch: auto-update-extension-versions + base: develop + labels: | + dependencies + automated + don't merge diff --git a/nix/ext/index_advisor.nix b/nix/ext/index_advisor.nix index 5892127142..59adc8e15e 100644 --- a/nix/ext/index_advisor.nix +++ b/nix/ext/index_advisor.nix @@ -31,7 +31,7 @@ let buildInputs = [ postgresql ]; src = fetchFromGitHub { - owner = "olirice"; + owner = "supabase"; repo = pname; rev = "v${version}"; inherit hash; diff --git a/nix/ext/pg-safeupdate.nix b/nix/ext/pg-safeupdate.nix index 452e9c2c5e..afd0af0f70 100644 --- a/nix/ext/pg-safeupdate.nix +++ b/nix/ext/pg-safeupdate.nix @@ -20,7 +20,7 @@ let src = fetchFromGitHub { owner = "eradman"; - repo = pname; + repo = "pg-safeupdate"; rev = version; inherit hash; }; diff --git a/nix/ext/pg_graphql/default.nix b/nix/ext/pg_graphql/default.nix index cbab08aa76..39d627400e 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 = { + perVersion = lib.mapAttrs (name: value: build name value.hash value.rust value.pgrx) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/pg_hashids.nix b/nix/ext/pg_hashids.nix index d9d4a34077..27e4e9a302 100644 --- a/nix/ext/pg_hashids.nix +++ b/nix/ext/pg_hashids.nix @@ -106,6 +106,9 @@ buildEnv { ''; passthru = { + perVersion = lib.mapAttrs ( + name: value: build name value.hash (value.revision or name) + ) versionsToUse; 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..cd71e85407 100644 --- a/nix/ext/pg_jsonschema/default.nix +++ b/nix/ext/pg_jsonschema/default.nix @@ -184,6 +184,7 @@ in ''; passthru = { + perVersion = lib.mapAttrs (name: value: build name value.hash value.rust value.pgrx) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/pg_stat_monitor.nix b/nix/ext/pg_stat_monitor.nix index 9a58464a66..15ebe773a9 100644 --- a/nix/ext/pg_stat_monitor.nix +++ b/nix/ext/pg_stat_monitor.nix @@ -110,6 +110,7 @@ buildEnv { ''; passthru = { + perVersion = lib.mapAttrs (name: value: build name value.hash value.revision) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/pgjwt.nix b/nix/ext/pgjwt.nix index 348b534c34..e752c8393e 100644 --- a/nix/ext/pgjwt.nix +++ b/nix/ext/pgjwt.nix @@ -82,6 +82,7 @@ buildEnv { pathsToLink = [ "/share/postgresql/extension" ]; passthru = { + perVersion = lib.mapAttrs (name: value: build name value.hash value.revision) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/pgmq/default.nix b/nix/ext/pgmq/default.nix index 7e2076a2f5..b5ee1a5bd3 100644 --- a/nix/ext/pgmq/default.nix +++ b/nix/ext/pgmq/default.nix @@ -36,7 +36,7 @@ let inherit pname version; buildInputs = [ postgresql ]; src = fetchFromGitHub { - owner = "tembo-io"; + owner = "pgmq"; repo = pname; rev = "v${version}"; inherit hash; diff --git a/nix/ext/pgtap.nix b/nix/ext/pgtap.nix index f283774dc8..042969d361 100644 --- a/nix/ext/pgtap.nix +++ b/nix/ext/pgtap.nix @@ -134,6 +134,7 @@ buildEnv { ''; passthru = { + perVersion = lib.mapAttrs (name: value: build name value.hash) supportedVersions; inherit versions numberOfVersions; pname = "${pname}-all"; version = diff --git a/nix/ext/plpgsql-check.nix b/nix/ext/plpgsql-check.nix index a9901eb0a5..5889ef14dd 100644 --- a/nix/ext/plpgsql-check.nix +++ b/nix/ext/plpgsql-check.nix @@ -139,6 +139,7 @@ buildEnv { ''; passthru = { + perVersion = lib.mapAttrs (name: value: build name value.hash value.revision) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit switch-ext-version latestOnly; diff --git a/nix/ext/rum.nix b/nix/ext/rum.nix index 31e6bae07b..97294afbb0 100644 --- a/nix/ext/rum.nix +++ b/nix/ext/rum.nix @@ -107,6 +107,7 @@ buildEnv { ''; passthru = { + perVersion = lib.mapAttrs (name: value: build name value.hash value.revision) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/timescaledb.nix b/nix/ext/timescaledb.nix index dbfb2a8365..c7aba32443 100644 --- a/nix/ext/timescaledb.nix +++ b/nix/ext/timescaledb.nix @@ -152,6 +152,9 @@ buildEnv { ]; passthru = { + perVersion = lib.mapAttrs ( + name: value: build name value.hash (value.revision or name) + ) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit switch-ext-version latestOnly; diff --git a/nix/ext/wal2json.nix b/nix/ext/wal2json.nix index b082301c82..65ff88eb42 100644 --- a/nix/ext/wal2json.nix +++ b/nix/ext/wal2json.nix @@ -106,6 +106,7 @@ pkgs.buildEnv { ''; passthru = { + perVersion = lib.mapAttrs (name: value: build name value.rev value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/wrappers/default.nix b/nix/ext/wrappers/default.nix index 3ae64afbd1..7476b0e9f2 100644 --- a/nix/ext/wrappers/default.nix +++ b/nix/ext/wrappers/default.nix @@ -366,6 +366,7 @@ in } ''; passthru = { + perVersion = packagesAttrSet; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; pname = "${pname}"; diff --git a/nix/packages/check-ext-versions.nix b/nix/packages/check-ext-versions.nix new file mode 100644 index 0000000000..8c5d4832d8 --- /dev/null +++ b/nix/packages/check-ext-versions.nix @@ -0,0 +1,17 @@ +{ + lib, + writers, + gh, + nix, +}: +writers.writeNuBin "check-ext-versions" { + makeWrapperArgs = [ + "--prefix" + "PATH" + ":" + (lib.makeBinPath [ + gh + nix + ]) + ]; +} (builtins.readFile ../tools/check-ext-versions.nu) diff --git a/nix/packages/default.nix b/nix/packages/default.nix index b7ad2b205b..062d1e31ca 100644 --- a/nix/packages/default.nix +++ b/nix/packages/default.nix @@ -106,6 +106,7 @@ inherit (self'.packages) overlayfs-on-package; }; sync-exts-versions = pkgs.callPackage ./sync-exts-versions.nix { inherit (inputs') nix-editor; }; + check-ext-versions = pkgs.callPackage ./check-ext-versions.nix { }; trigger-nix-build = pkgs.callPackage ./trigger-nix-build.nix { }; update-readme = pkgs.callPackage ./update-readme.nix { }; supabase-cli = pkgs.callPackage ./supabase-cli.nix { }; diff --git a/nix/packages/extension-catalog.nix b/nix/packages/extension-catalog.nix index d8d5591216..d1b1acfdb2 100644 --- a/nix/packages/extension-catalog.nix +++ b/nix/packages/extension-catalog.nix @@ -123,6 +123,18 @@ lib.recurseIntoAttrs (lib.mapAttrs (_: lib.recurseIntoAttrs) wrappers) ) ) perMajor; + + extUpdateRepos = lib.filterAttrs (_: v: v != null) ( + lib.mapAttrs ( + _: pkg: + let + pv = pkg.perVersion or { }; + entry = if pv != { } then pv.${builtins.head (builtins.attrNames pv)} else { }; + src = entry.src or { }; + in + if (src ? owner) && (src ? repo) then "${src.owner}/${src.repo}" else null + ) self'.legacyPackages."psql_15".exts + ); in { packages = catalogs // { @@ -163,6 +175,6 @@ ''; }; }; - legacyPackages = catalogs // versions; + legacyPackages = catalogs // versions // { inherit extUpdateRepos; }; }; } diff --git a/nix/tools/check-ext-versions.nu b/nix/tools/check-ext-versions.nu new file mode 100755 index 0000000000..16c43caa44 --- /dev/null +++ b/nix/tools/check-ext-versions.nu @@ -0,0 +1,144 @@ +#!/usr/bin/env nu + +const OVERRIDES = { + pg_graphql: {noHash: true} + pg_plan_filter: {repo: "pgexperts/pg_plan_filter"} + pgroonga: {repo: "pgroonga/pgroonga", noHash: true} + postgis: {repo: "postgis/postgis", noHash: true} + wrappers: {noHash: true} +} + +const FAKE_HASH = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + +def run [command: list] { + run-external ...$command | complete + | if $in.exit_code == 0 { $in.stdout | str trim } else { null } +} + +def parse-version [text: string] { + $text + | parse --regex '^(\d+(?:\.\d+)*)' + | if ($in | is-empty) { + [0] + } else { + $in.0.capture0 | split row "." | each { into int } + } +} + +def is-newer [current: list, candidate: list] { + let highest = ([{v: $current}, {v: $candidate}] | sort-by v | last | get v) + $candidate != $current and $highest == $candidate +} + +def best-candidate [tags: list, repo: string] { + let prefixed = ("^" + $repo + "[-_](\\d+(?:[._]\\d+){1,3})$") + let candidates = ( + $tags + | each { |tag| + let dtag = ($tag | str downcase) + let match = ( + $dtag + | parse --regex '^(?:v|ver_)?(\d+(?:\.\d+){0,3})$' + | if ($in | is-empty) { $dtag | parse --regex $prefixed } else { $in } + ) + if ($match | is-empty) { + null + } else { + let version = ($match.0.capture0 | str replace --all "_" ".") + {v: (parse-version $version), tag: $tag} + } + } + | compact + ) + if ($candidates | is-empty) { null } else { $candidates | sort-by v | last } +} + +let system = (run ["nix" "eval" "--impure" "--raw" "--expr" "builtins.currentSystem"]) +if $system == null { + error make {msg: "nix eval of builtins.currentSystem failed"} +} + +let derived = ( + run ["nix" "eval" "--json" $".#legacyPackages.($system).extUpdateRepos"] + | if $in == null { + error make {msg: "nix eval of derived repos failed"} + } else { + $in | from json + } +) + +let repo_root = (run ["git" "rev-parse" "--show-toplevel"]) +let versions_file = ($repo_root | path join "nix/ext/versions.json") +mut versions = (open $versions_file) +mut changed = false + +for ext in ($versions | columns) { + let override = ($OVERRIDES | get -o $ext) + let repo_slug = ($override.repo? | default ($derived | get -o $ext)) + if $repo_slug == null { + let hint = "add it to OVERRIDES in check-ext-versions.nu" + error make {msg: $"no update source for ($ext) - ($hint)"} + } + let no_hash = ($override.noHash? | default false) + + let parts = ($repo_slug | split row "/") + let owner = $parts.0 + let repo = $parts.1 + + let tags = ( + run ["gh" "api" $"repos/($owner)/($repo)/tags" "--paginate"] + | if $in == null { null } else { $in | from json | get name } + ) + if $tags == null { + print $"skip ($ext): tags lookup failed" + continue + } + + let candidate = (best-candidate $tags $repo) + if $candidate == null { + print $"skip ($ext): no clean version tags" + continue + } + + let entries = ($versions | get $ext) + let current_key = ( + $entries + | columns + | each { |k| {k: $k, v: (parse-version $k)} } + | sort-by v + | last + | get k + ) + if not (is-newer (parse-version $current_key) $candidate.v) { continue } + + let sri_hash = if $no_hash { + $FAKE_HASH + } else { + let url = $"https://github.com/($owner)/($repo)/archive/($candidate.tag).tar.gz" + run ["nix-prefetch-url" "--type" "sha256" "--unpack" $url] + | if $in == null { + null + } else { + run ["nix" "hash" "to-sri" "--type" "sha256" ($in | lines | last)] + } + } + if $sri_hash == null { + print $"skip ($ext): prefetch failed for ($candidate.tag)" + continue + } + + let version_str = ($candidate.v | each { into string } | str join ".") + let entry = { + postgresql: ($entries | get $current_key | get postgresql) + revision: $candidate.tag + rev: $candidate.tag + hash: $sri_hash + } + $versions = ($versions | upsert $ext ($entries | upsert $version_str $entry)) + $changed = true + print $"updated ($ext) -> ($version_str) \(($candidate.tag)\)" +} + +if $changed { + $versions | to json --indent 2 | $"($in)\n" | save -f $versions_file +}