From 42efa9095bbbedd102518bfea2a3e879db9e25fc Mon Sep 17 00:00:00 2001 From: Dave Page Date: Mon, 26 Oct 2026 20:32:43 +0000 Subject: [PATCH 1/4] Run the acceptance test against each published nightly snapshot. Only the release candidate ran it, so an unsigned libcrypto and libssl in the Windows installer went unnoticed in every nightly from #10355 until the first candidate. The snapshot reaches ftp.postgresql.org on the hourly mirror sync, so a new job waits until everything the test fetches is being served before calling it. --- .github/workflows/snapshot.yml | 80 ++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml index 71a6d33b10b..d7a2eedbc2f 100644 --- a/.github/workflows/snapshot.yml +++ b/.github/workflows/snapshot.yml @@ -463,3 +463,83 @@ jobs: else publish snapshot-purge fi + + # The published snapshot reaches ftp.postgresql.org on the hourly sync from + # the download server, not when the publish step finishes, so the acceptance + # test cannot simply follow it. This waits until everything that test will + # fetch is being served, the signatures and repository indexes included, + # since those are written last on the server. + # + # Each request carries a throwaway query string. The mirrors sit behind + # Varnish, and a plain request for a file that has not arrived yet could + # leave a cached 404 for exactly the URL the acceptance test asks for next. + mirrored: + needs: [location, windows, assemble] + if: >- + always() + && needs.assemble.result == 'success' + && (github.event_name == 'schedule' || inputs.publish) + runs-on: ubuntu-24.04 + timeout-minutes: 180 + steps: + - name: Wait for the snapshot to reach the mirrors + env: + BASE_URL: https://ftp.postgresql.org/pub/pgadmin/pgadmin4/snapshots/${{ needs.location.outputs.name }} + # The Windows runner is allowed to be offline, in which case the + # snapshot has no installer and waiting for one would only time out. + # The acceptance test still fails for it, which is right: the run + # is already red from the Windows job. + WINDOWS: ${{ needs.windows.result }} + run: | + set -euo pipefail + + fetch() { curl -fsS "${BASE_URL}/$1?mirror-check=$(date +%s)"; } + + complete() { + local listing name + listing=$(fetch '') || return 1 + local wanted=( + 'pgadmin4-[0-9.]+\.tar\.gz\.asc' + 'pgadmin4-[0-9.]+-py3-none-any\.whl\.asc' + 'pgadmin4-[0-9.]+-docs\.tar\.gz\.asc' + 'pgadmin4-[0-9.]+\.pdf\.asc' + 'pgadmin4-[0-9.]+\.epub\.asc' + 'pgadmin4-[0-9.]+-arm64\.dmg' + 'pgadmin4-[0-9.]+-x64\.dmg' + ) + [ "${WINDOWS}" = "success" ] && wanted+=('pgadmin4-[0-9.]+-x64\.exe') + for name in "${wanted[@]}"; do + grep -Eq "\"${name}\"" <<< "${listing}" || { + echo " not yet: ${name}"; return 1; } + done + + for name in bookworm trixie jammy noble resolute; do + fetch "apt/${name}/dists/pgadmin4/InRelease" >/dev/null || { + echo " not yet: apt/${name}"; return 1; } + done + for name in redhat/rhel-9 redhat/rhel-10 fedora/fedora-43 fedora/fedora-44; do + fetch "yum/${name}-x86_64/repodata/repomd.xml.asc" >/dev/null || { + echo " not yet: yum/${name}"; return 1; } + done + } + + # Up to two and a half hours: two sync intervals and some slack. + for attempt in $(seq 1 30); do + echo "Check ${attempt} of ${BASE_URL}" + if complete; then + echo "The snapshot is being served." + exit 0 + fi + sleep 300 + done + echo "The snapshot had not reached the mirrors after 150 minutes." >&2 + exit 1 + + # Installing from the published snapshot, as the release candidate does, + # so that a defect only visible in what a user downloads shows up the next + # morning rather than when a release is being cut. + acceptance: + needs: [location, mirrored] + uses: ./.github/workflows/acceptance-test.yml + with: + base-url: https://ftp.postgresql.org/pub/pgadmin/pgadmin4/snapshots/${{ needs.location.outputs.name }} From 802e2c9d737b859f52013bc4589e5ba4334c925e Mon Sep 17 00:00:00 2001 From: Dave Page Date: Wed, 4 Nov 2026 11:33:58 +0000 Subject: [PATCH 2/4] Bound each mirror request, and describe the snapshot run in the acceptance test. --- .github/workflows/acceptance-test.yml | 7 ++++--- .github/workflows/snapshot.yml | 7 ++++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/acceptance-test.yml b/.github/workflows/acceptance-test.yml index 5573daaef68..a6526049e80 100644 --- a/.github/workflows/acceptance-test.yml +++ b/.github/workflows/acceptance-test.yml @@ -12,9 +12,10 @@ # wrong or the architecture is missing from Release, the install fails, which # is the point. # -# It runs against a release candidate on the development server, which serves -# its tree the moment it is written. The download server sits behind an hourly -# sync to the mirrors, so testing there would mostly measure the sync. +# It runs against each release candidate on the development server, which +# serves its tree the moment it is written, and against each published nightly +# snapshot on ftp.postgresql.org, where snapshot.yml first waits for the hourly +# sync to the mirrors to deliver everything this test fetches. name: Acceptance test diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml index d7a2eedbc2f..80ee9a3f72f 100644 --- a/.github/workflows/snapshot.yml +++ b/.github/workflows/snapshot.yml @@ -493,7 +493,12 @@ jobs: run: | set -euo pipefail - fetch() { curl -fsS "${BASE_URL}/$1?mirror-check=$(date +%s)"; } + # Bounded, so that one stalled connection to a mirror cannot use up + # the job's time limit and leave no polls after it. + fetch() { + curl -fsS --connect-timeout 10 --max-time 60 \ + "${BASE_URL}/$1?mirror-check=$(date +%s)" + } complete() { local listing name From e048511494596682ab0fb7d208621f91490d8403 Mon Sep 17 00:00:00 2001 From: Dave Page Date: Wed, 4 Nov 2026 11:40:22 +0000 Subject: [PATCH 3/4] Limit the mirror wait on elapsed time rather than on attempts. --- .github/workflows/snapshot.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml index 80ee9a3f72f..be7a6029f1a 100644 --- a/.github/workflows/snapshot.yml +++ b/.github/workflows/snapshot.yml @@ -528,13 +528,20 @@ jobs: done } - # Up to two and a half hours: two sync intervals and some slack. - for attempt in $(seq 1 30); do + # Up to two and a half hours: two sync intervals and some slack. The + # limit is on elapsed time rather than on attempts, since a check can + # make ten requests of up to a minute each against a stalled mirror, + # and counting attempts would then run into the job's own timeout. + deadline=$((SECONDS + 150 * 60)) + attempt=0 + while [ "${SECONDS}" -lt "${deadline}" ]; do + attempt=$((attempt + 1)) echo "Check ${attempt} of ${BASE_URL}" if complete; then echo "The snapshot is being served." exit 0 fi + [ $((SECONDS + 300)) -lt "${deadline}" ] || break sleep 300 done echo "The snapshot had not reached the mirrors after 150 minutes." >&2 From 6b867b5abe07ce4a6ccf68e3f1bfdbf4b7dba0a2 Mon Sep 17 00:00:00 2001 From: Dave Page Date: Wed, 4 Nov 2026 11:46:05 +0000 Subject: [PATCH 4/4] Run a final mirror check at the deadline rather than skipping it. --- .github/workflows/snapshot.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml index be7a6029f1a..edf5e288f8c 100644 --- a/.github/workflows/snapshot.yml +++ b/.github/workflows/snapshot.yml @@ -534,15 +534,21 @@ jobs: # and counting attempts would then run into the job's own timeout. deadline=$((SECONDS + 150 * 60)) attempt=0 - while [ "${SECONDS}" -lt "${deadline}" ]; do + while :; do attempt=$((attempt + 1)) echo "Check ${attempt} of ${BASE_URL}" if complete; then echo "The snapshot is being served." exit 0 fi - [ $((SECONDS + 300)) -lt "${deadline}" ] || break - sleep 300 + [ "${SECONDS}" -lt "${deadline}" ] || break + # The last wait is cut short, so that a final check still runs + # at the deadline rather than being skipped. + wait=$((deadline - SECONDS)) + if [ "${wait}" -gt 300 ]; then + wait=300 + fi + sleep "${wait}" done echo "The snapshot had not reached the mirrors after 150 minutes." >&2 exit 1