From 68bcf32cadd908830f8ed6bdd1be94dc760d0b84 Mon Sep 17 00:00:00 2001 From: Austin Burdine Date: Fri, 24 Jul 2026 13:02:38 -0400 Subject: [PATCH] Fixed update workflow to be re-runnable no ref - if the official-images update fails, there's not a good way to re-run this workflow - update the official-images push part of the script to be idempotent so it can be re-ran on failure --- .github/workflows/update.yml | 72 ++++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 20 deletions(-) diff --git a/.github/workflows/update.yml b/.github/workflows/update.yml index 84dc1452..1fd525e8 100644 --- a/.github/workflows/update.yml +++ b/.github/workflows/update.yml @@ -23,6 +23,12 @@ defaults: run: shell: 'bash -Eeuo pipefail -x {0}' +# the job pushes to the default branch and force-pushes a shared branch on the +# official-images fork; never let two runs do that at the same time +concurrency: + group: update-dockerfiles + cancel-in-progress: false + jobs: update: name: Update Dockerfiles @@ -35,6 +41,11 @@ jobs: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: fetch-depth: 0 + # always work from the current tip of the default branch, not from the + # sha the event was created with: a re-run of this workflow reuses the + # original event sha, which would re-apply (and re-push) a commit an + # earlier run already landed + ref: ${{ github.event.repository.default_branch }} # don't leave the token in .git/config for later steps (e.g. the bashbrew # checkout and the official-images clone); the push below authenticates explicitly persist-credentials: false @@ -83,26 +94,43 @@ jobs: # build a message following the existing convention: # "Update to " for a Ghost bump # "Update to CLI " for a Ghost-CLI bump + # + # when update.sh produced nothing (e.g. this is a re-run after an earlier + # run already committed), reuse the message/sha of the commit that last + # touched versions.json so the upstream PR still describes the right bump - name: Compute commit message id: message - if: steps.changes.outputs.changed == 'true' + env: + CHANGED: ${{ steps.changes.outputs.changed }} run: | - old="$(git show HEAD:versions.json 2>/dev/null || echo '{}')" - message="$(jq --raw-output --null-input \ - --argjson old "$old" \ - --argjson new "$(cat versions.json)" ' - [ $new | to_entries[] | .key as $k | .value as $v - | ( if ($old[$k].version // null) != $v.version - then "Update \($k) to \($v.version)" else empty end ), - ( if (($old[$k].cli.version) // null) != $v.cli.version - then "Update \($k) to CLI \($v.cli.version)" else empty end ) - ] - | join(", ") - ')" + if [ "$CHANGED" = 'true' ]; then + old="$(git show HEAD:versions.json 2>/dev/null || echo '{}')" + message="$(jq --raw-output --null-input \ + --argjson old "$old" \ + --argjson new "$(cat versions.json)" ' + [ $new | to_entries[] | .key as $k | .value as $v + | ( if ($old[$k].version // null) != $v.version + then "Update \($k) to \($v.version)" else empty end ), + ( if (($old[$k].cli.version) // null) != $v.cli.version + then "Update \($k) to CLI \($v.cli.version)" else empty end ) + ] + | join(", ") + ')" + sha='' + else + message="$(git log -1 --pretty=%s -- versions.json)" + sha="$(git log -1 --pretty=%H -- versions.json)" + fi if [ -z "$message" ]; then message='Update Dockerfiles' fi - echo "message=$message" >> "$GITHUB_OUTPUT" + if [ -z "$sha" ]; then + sha="$(git rev-parse HEAD)" + fi + { + echo "message=$message" + echo "sha=$sha" + } >> "$GITHUB_OUTPUT" - name: Print diff (dry run) if: steps.inputs.outputs.dry_run == 'true' @@ -127,18 +155,21 @@ jobs: # bashbrew is required by generate-stackbrew-library.sh to resolve parent image arches - name: Install Bashbrew - if: steps.inputs.outputs.dry_run != 'true' && steps.changes.outputs.changed == 'true' && steps.inputs.outputs.make_image_pr == 'true' + if: steps.inputs.outputs.dry_run != 'true' && steps.inputs.outputs.make_image_pr == 'true' uses: ./.github/actions/bashbrew/ + # deliberately not gated on steps.changes: re-running the workflow after a + # failed push/PR here must be able to retry this step on its own, and the + # library/ghost diff below is the real no-op guard - name: Open official-images PR - if: steps.inputs.outputs.dry_run != 'true' && steps.changes.outputs.changed == 'true' && steps.inputs.outputs.make_image_pr == 'true' + if: steps.inputs.outputs.dry_run != 'true' && steps.inputs.outputs.make_image_pr == 'true' # avoid -x here so the auth token is not echoed into the logs shell: 'bash -Eeuo pipefail {0}' env: GH_TOKEN: ${{ secrets.OFFICIAL_IMAGES_PR_TOKEN }} FORK_REPO: ${{ steps.inputs.outputs.official_images_fork_repo }} COMMIT_MESSAGE: ${{ steps.message.outputs.message }} - HEAD_SHA: ${{ steps.commit.outputs.sha }} + HEAD_SHA: ${{ steps.commit.outputs.sha || steps.message.outputs.sha }} run: | # configure git to authenticate via the provided token (no token in URLs/logs) gh auth setup-git @@ -158,14 +189,15 @@ jobs: git config user.name 'Ghost-Slimer' git config user.email 'ghost-slimer@users.noreply.github.com' # always (re)create the update-ghost branch and force-push, overriding any existing one - git checkout -b update-ghost + git checkout -B update-ghost git add library/ghost git commit -m "$COMMIT_MESSAGE" git push --force origin update-ghost forkOwner="${FORK_REPO%%/*}" - if [ -n "$(gh pr list --repo docker-library/official-images --head "$forkOwner:update-ghost" --json number --jq '.[].number')" ]; then - echo "PR already exists for $forkOwner:update-ghost; force-pushed branch is enough." + existingPr="$(gh pr list --repo docker-library/official-images --head "$forkOwner:update-ghost" --state open --json url --jq '.[].url')" + if [ -n "$existingPr" ]; then + echo "PR already exists for $forkOwner:update-ghost ($existingPr); force-pushed branch is enough." else gh pr create \ --repo docker-library/official-images \