From ae4b081b3b0091bf16dd43a86466466dbb02c536 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Thu, 29 Oct 2026 12:58:58 +0100 Subject: [PATCH 1/8] ci: Run e2e tests on PRs with a local image registry Co-Authored-By: Claude Opus 5.5 --- .../fix-security-vulnerability/SKILL.md | 13 ++- .agents/skills/verify-dist/SKILL.md | 6 +- .github/actions/use-local-image/action.yml | 55 +++++++++++ .github/workflows/build.yml | 97 ++++++++++++++++--- .pre-commit-config.yaml | 5 - dist/index.js | 2 +- docs/development.md | 18 ++-- package.json | 1 - scripts/set-docker-tag-from-branch.sh | 10 -- 9 files changed, 156 insertions(+), 51 deletions(-) create mode 100644 .github/actions/use-local-image/action.yml delete mode 100755 scripts/set-docker-tag-from-branch.sh diff --git a/.agents/skills/fix-security-vulnerability/SKILL.md b/.agents/skills/fix-security-vulnerability/SKILL.md index d8ff8008..931203da 100644 --- a/.agents/skills/fix-security-vulnerability/SKILL.md +++ b/.agents/skills/fix-security-vulnerability/SKILL.md @@ -56,14 +56,14 @@ make # installs yarn deps AND runs 'pre-commit install' pre-commit install ``` -The pre-commit config (`.pre-commit-config.yaml`) runs formatters, linters, and a `set-docker-tag-from-branch` hook that rewrites `action.yml` so the Docker tag matches the current branch name. **This is not optional.** CI's `prepare-docker` job rejects any PR whose `action.yml` Docker tag still matches a semver like `3.6.0`. The pre-commit hook is what keeps the tag in sync on feature branches; CI does **not** install or run pre-commit itself. +The pre-commit config (`.pre-commit-config.yaml`) runs formatters and linters. CI does **not** install or run pre-commit itself. ### Hook-modified files: re-stage, re-commit (never `--amend`) -On the first `git commit`, the `set-docker-tag-from-branch` hook will usually modify `action.yml`. The commit fails, and the hook's changes are left unstaged. Recover by: +If a hook modifies files, the commit fails and the hook's changes are left unstaged. Recover by: ```bash -git add action.yml +git add git commit -m "" # a NEW commit; do not use --amend ``` @@ -123,10 +123,9 @@ Co-Authored-By: EOF )" -# 4. If the 'set-docker-tag-from-branch' hook rewrote action.yml (it usually -# does on the first commit of a branch), the commit above fails. Re-stage -# and re-commit — DO NOT use --amend. -git add action.yml +# 4. If a pre-commit hook modified files, the commit above fails. Re-stage +# and re-commit. DO NOT use --amend. +git add git commit -m "" ``` diff --git a/.agents/skills/verify-dist/SKILL.md b/.agents/skills/verify-dist/SKILL.md index e26d8708..8bed1b81 100644 --- a/.agents/skills/verify-dist/SKILL.md +++ b/.agents/skills/verify-dist/SKILL.md @@ -61,15 +61,13 @@ Regenerates dist/index.js. Whoever runs the follow-up commit needs the repo's pre-commit hooks installed. Verify with `ls .git/hooks/pre-commit`; if missing, run `make` (installs yarn deps + `pre-commit install`) or `pre-commit install`. -The `set-docker-tag-from-branch` hook will rewrite `action.yml` on the first commit of a branch and fail the commit. Recover by staging `action.yml` and committing again (**never `--amend`** — the hook-failed commit never landed): +If a hook modifies files, the commit fails. Recover by staging the modified files and committing again (**never `--amend`**, because the hook-failed commit never landed): ```bash -git add action.yml +git add git commit -m "" ``` -CI does **not** run pre-commit. CI's `prepare-docker` job rejects PRs where `action.yml`'s Docker tag still matches a semver like `3.6.0`, so keeping the tag in sync via the local hook is what prevents the CI failure. - ## Notes - The CI check runs `git diff --ignore-space-at-eol dist/` — whitespace-only diffs pass, anything else fails. Match that locally. diff --git a/.github/actions/use-local-image/action.yml b/.github/actions/use-local-image/action.yml new file mode 100644 index 00000000..1e269211 --- /dev/null +++ b/.github/actions/use-local-image/action.yml @@ -0,0 +1,55 @@ +name: 'Use local action image' +description: |- + Loads the action image built in this workflow run into a local registry + and points the checked-out action.yml to it. PR runs use this, because + they cannot push to ghcr.io (fork PRs get a read-only token). + +inputs: + path: + description: Directory of the checked-out action that the tests run. + default: '.' + required: false + +runs: + using: 'composite' + steps: + - name: Download image + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + name: action-release-image-${{ runner.arch == 'ARM64' && 'arm64' || 'amd64' }} + # Relative path, because `runner.temp` is a host path in container jobs. + path: .action-release-image + + # Container jobs have the host docker socket mounted but usually no docker CLI. + - name: Install docker CLI + shell: bash + run: | + if command -v docker &> /dev/null; then + exit 0 + fi + curl -fsSL "https://download.docker.com/linux/static/stable/$(uname -m)/docker-29.9.1.tgz" \ + | tar -xz -C /usr/local/bin --strip-components=1 docker/docker + + # The runner always pulls `docker://` images, so a local copy is not enough. + # The daemon trusts registries on localhost without TLS. + - name: Push image to local registry + shell: bash + run: | + docker run -d --rm -p 5000:5000 --name action-release-registry \ + registry:2@sha256:a3d8aaa63ed8681a604f1dea0aa03f100d5895b6a58ace528858a7b332415373 + docker load -i .action-release-image/image.tar + rm -rf .action-release-image + # Retry until the registry is ready. A curl check does not work in + # container jobs, because there localhost is the job container. + for i in $(seq 1 10); do + docker push localhost:5000/action-release-image:pr && exit 0 + sleep 2 + done + exit 1 + + - name: Point action.yml to local image + shell: bash + working-directory: ${{ inputs.path }} + run: | + sed -i -e 's|docker://ghcr.io/getsentry/action-release-image:.*|docker://localhost:5000/action-release-image:pr|' action.yml + grep -q 'docker://localhost:5000/action-release-image:pr' action.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 70ee8de3..e228f438 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,6 +23,7 @@ env: jobs: prepare-docker: name: Prepare docker tag + if: github.event_name == 'push' runs-on: ubuntu-latest permissions: contents: write @@ -41,14 +42,6 @@ jobs: else TAG=$(yq '... | select(has("uses") and .uses | test("docker://ghcr.io/getsentry/action-release-image:.*")) | .uses' action.yml | awk -F':' '{print $3}') echo "docker_tag=$TAG" >> $GITHUB_OUTPUT - - if [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then - if [[ "$TAG" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "Error: docker_tag $TAG matching format MAJOR.MINOR.PATCH is not allowed inside pull requests." - echo "Please rename the docker tag in action.yml and try again." - exit 1 - fi - fi fi - name: Get auth token @@ -69,6 +62,7 @@ jobs: docker-build-platform: name: Build & publish Docker images needs: prepare-docker + if: github.event_name == 'push' runs-on: ${{ matrix.platform.runner }} permissions: packages: write @@ -123,6 +117,7 @@ jobs: docker-build: name: Merge multi-arch Docker images needs: [prepare-docker, docker-build-platform] + if: github.event_name == 'push' runs-on: ubuntu-latest permissions: packages: write @@ -149,6 +144,46 @@ jobs: grep -qx linux/amd64 <<< "$PLATFORMS" grep -qx linux/arm64 <<< "$PLATFORMS" + # PRs (including fork PRs, which get a read-only token) do not push to ghcr.io. + # The e2e tests load this image into a local registry, see `use-local-image`. + docker-build-pr: + name: Build Docker image for PR + if: github.event_name == 'pull_request' + runs-on: ${{ matrix.platform.runner }} + permissions: + contents: read + strategy: + matrix: + platform: + - name: linux/amd64 + arch: amd64 + runner: ubuntu-latest + - name: linux/arm64 + arch: arm64 + runner: ubuntu-24.04-arm + steps: + - name: Checkout repo + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 + + - name: Build + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 + with: + platforms: ${{ matrix.platform.name }} + tags: localhost:5000/action-release-image:pr + outputs: type=docker,dest=${{ runner.temp }}/image.tar + cache-from: ghcr.io/${{ github.repository_owner }}/action-release-image:master + target: app + + - name: Upload image + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: action-release-image-${{ matrix.platform.arch }} + path: ${{ runner.temp }}/image.tar + retention-days: 1 + lint: runs-on: ubuntu-latest permissions: @@ -174,7 +209,9 @@ jobs: ############# test-create-staging-release-per-push: - needs: docker-build + needs: [docker-build, docker-build-pr] + # Needs SENTRY_AUTH_TOKEN, which fork PRs do not get. + if: ${{ !failure() && !cancelled() && !github.event.pull_request.head.repo.fork }} strategy: matrix: os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-latest] @@ -187,6 +224,10 @@ jobs: with: fetch-depth: 0 + - name: Use local action image + if: github.event_name == 'pull_request' && runner.os == 'Linux' + uses: ./.github/actions/use-local-image + - name: Create a staging release uses: ./ env: @@ -196,7 +237,9 @@ jobs: ignore_missing: true test-runs-on-container: - needs: docker-build + needs: [docker-build, docker-build-pr] + # Needs SENTRY_AUTH_TOKEN, which fork PRs do not get. + if: ${{ !failure() && !cancelled() && !github.event.pull_request.head.repo.fork }} runs-on: ubuntu-latest permissions: contents: read @@ -208,6 +251,10 @@ jobs: with: fetch-depth: 0 + - name: Use local action image + if: github.event_name == 'pull_request' && runner.os == 'Linux' + uses: ./.github/actions/use-local-image + - name: Create a staging release uses: ./ env: @@ -217,7 +264,8 @@ jobs: ignore_missing: true test-mock-release: - needs: docker-build + needs: [docker-build, docker-build-pr] + if: ${{ !failure() && !cancelled() }} strategy: matrix: os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-latest] @@ -230,6 +278,10 @@ jobs: with: fetch-depth: 0 + - name: Use local action image + if: github.event_name == 'pull_request' && runner.os == 'Linux' + uses: ./.github/actions/use-local-image + - name: Mock creating a Sentry release uses: ./ env: @@ -238,7 +290,8 @@ jobs: environment: production test-mock-release-working-directory: - needs: docker-build + needs: [docker-build, docker-build-pr] + if: ${{ !failure() && !cancelled() }} strategy: matrix: os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-latest] @@ -259,6 +312,12 @@ jobs: fetch-depth: 0 path: test/ + - name: Use local action image + if: github.event_name == 'pull_request' && runner.os == 'Linux' + uses: ./main/.github/actions/use-local-image + with: + path: main + - name: Mock creating a Sentry release in a different directory uses: ./main env: @@ -268,7 +327,8 @@ jobs: working_directory: ./test test-node-version-preserved: - needs: docker-build + needs: [docker-build, docker-build-pr] + if: ${{ !failure() && !cancelled() }} strategy: matrix: os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-latest] @@ -282,6 +342,10 @@ jobs: with: fetch-depth: 0 + - name: Use local action image + if: github.event_name == 'pull_request' && runner.os == 'Linux' + uses: ./.github/actions/use-local-image + - name: Setup Node uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: @@ -316,7 +380,8 @@ jobs: echo "SUCCESS: Node version preserved" test-manual-commit-range: - needs: docker-build + needs: [docker-build, docker-build-pr] + if: ${{ !failure() && !cancelled() }} strategy: matrix: @@ -330,6 +395,10 @@ jobs: with: fetch-depth: 0 + - name: Use local action image + if: github.event_name == 'pull_request' && runner.os == 'Linux' + uses: ./.github/actions/use-local-image + - name: Create a release with manual commit range uses: ./ env: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e51a717f..4e1d5373 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,8 +21,3 @@ repos: entry: yarn lint language: system pass_filenames: false - - id: set-docker-tag-from-branch - name: Set docker tag in action.yml from current git branch - entry: yarn set-docker-tag-from-branch - language: system - pass_filenames: false diff --git a/dist/index.js b/dist/index.js index 75edd0d8..6186ba24 100644 --- a/dist/index.js +++ b/dist/index.js @@ -100637,7 +100637,7 @@ const BROWSER_PAGELOAD_SPAN_OP="pageload",BROWSER_NAVIGATION_SPAN_OP="navigation /***/ ((module) => { "use strict"; -module.exports = JSON.parse('{"name":"action-release","version":"3.7.2","private":true,"description":"GitHub Action for creating a release on Sentry","main":"dist/index.js","scripts":{"all":"yarn run format && yarn run lint && yarn run build && yarn test","build":"ncc build src/main.ts -e @sentry/cli","format":"prettier --write **/*.ts **/*.md","format-check":"prettier --check **/*.ts **/*.md","lint":"eslint src/**/*.ts","set-docker-tag":"./scripts/set-docker-tag.sh","set-docker-tag-from-branch":"./scripts/set-docker-tag-from-branch.sh","start":"node dist/index.js","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/getsentry/action-release.git"},"keywords":["actions","sentry","release"],"author":"Sentry","license":"MIT","dependencies":{"@actions/core":"^2.0.3","@sentry/cli":"^2.58.6","@sentry/node":"^10.71.0"},"devDependencies":{"@types/jest":"^29.5.6","@types/node":"^24.0.0","@typescript-eslint/parser":"^6.9.0","@vercel/ncc":"^0.38.1","eslint":"^8.52.0","eslint-plugin-github":"^4.10.1","eslint-plugin-jest":"^27.4.3","jest":"^29.7.0","jest-circus":"^29.7.0","js-yaml":"^4.3.2","prettier":"^3.0.3","ts-jest":"^29.1.1","typescript":"^5.2.2"},"resolutions":{"**/@istanbuljs/load-nyc-config/js-yaml":"3.15.2"},"volta":{"node":"24.22.1","yarn":"1.22.4"}}'); +module.exports = JSON.parse('{"name":"action-release","version":"3.7.2","private":true,"description":"GitHub Action for creating a release on Sentry","main":"dist/index.js","scripts":{"all":"yarn run format && yarn run lint && yarn run build && yarn test","build":"ncc build src/main.ts -e @sentry/cli","format":"prettier --write **/*.ts **/*.md","format-check":"prettier --check **/*.ts **/*.md","lint":"eslint src/**/*.ts","set-docker-tag":"./scripts/set-docker-tag.sh","start":"node dist/index.js","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/getsentry/action-release.git"},"keywords":["actions","sentry","release"],"author":"Sentry","license":"MIT","dependencies":{"@actions/core":"^2.0.3","@sentry/cli":"^2.58.6","@sentry/node":"^10.71.0"},"devDependencies":{"@types/jest":"^29.5.6","@types/node":"^24.0.0","@typescript-eslint/parser":"^6.9.0","@vercel/ncc":"^0.38.1","eslint":"^8.52.0","eslint-plugin-github":"^4.10.1","eslint-plugin-jest":"^27.4.3","jest":"^29.7.0","jest-circus":"^29.7.0","js-yaml":"^4.3.2","prettier":"^3.0.3","ts-jest":"^29.1.1","typescript":"^5.2.2"},"resolutions":{"**/@istanbuljs/load-nyc-config/js-yaml":"3.15.2"},"volta":{"node":"24.22.1","yarn":"1.22.4"}}'); /***/ }) diff --git a/docs/development.md b/docs/development.md index d8546100..a007eed6 100644 --- a/docs/development.md +++ b/docs/development.md @@ -51,15 +51,15 @@ Telemetry for internal development is collected using `@sentry/node`, see `src/t Members of this repo will not have to set anything up since [the integration](https://sentry-ecosystem.sentry.io/settings/developer-settings/end-to-end-action-release-integration-416eb2/) is already set-up. Just open the PR and you will see [a release created](https://sentry-ecosystem.sentry.io/releases/?project=4505075304693760) for your PR. 1. Create a branch -2. Run `yarn set-docker-tag-from-branch` to set a docker tag based on your github branch name. This is important so that the action gets its own Docker image, allowing you to test the action in a different repo. -3. Make changes -4. If possible, add unit and E2E tests (inside `.github/workflows/build.yml`) -5. Run `yarn install` to install deps -6. Run `yarn build` to build the action -7. Commit the changes and the build inside `dist/` - -If you forget to run `yarn set-docker-tag-from-branch` the repo's pre-commit hooks will do it for you and fail the commit. -Just add the changes to staging and commit again. +2. Make changes +3. If possible, add unit and E2E tests (inside `.github/workflows/build.yml`) +4. Run `yarn install` to install deps +5. Run `yarn build` to build the action +6. Commit the changes and the build inside `dist/` + +Do not change the Docker image tag in `action.yml`. PR builds do not publish an image to ghcr.io. +The E2E tests load the image of the PR into a local registry (see `.github/actions/use-local-image`). +To use your changes from a different repo, merge them and use `getsentry/action-release@master`. ## Testing diff --git a/package.json b/package.json index bd9812a4..df86dea8 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,6 @@ "format-check": "prettier --check **/*.ts **/*.md", "lint": "eslint src/**/*.ts", "set-docker-tag": "./scripts/set-docker-tag.sh", - "set-docker-tag-from-branch": "./scripts/set-docker-tag-from-branch.sh", "start": "node dist/index.js", "test": "jest" }, diff --git a/scripts/set-docker-tag-from-branch.sh b/scripts/set-docker-tag-from-branch.sh deleted file mode 100755 index f626b04d..00000000 --- a/scripts/set-docker-tag-from-branch.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -set -eux - -# Extract the branch name from git and replace all non-alphanumerical characters with `-` -BRANCH=$(git rev-parse --abbrev-ref HEAD | sed 's/[^a-zA-Z0-9-]/-/g') - -SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -cd $SCRIPT_DIR - -./set-docker-tag.sh $BRANCH From faeffef53c674c7b14022c3b4b75904b21d58cc9 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Thu, 29 Oct 2026 14:05:42 +0100 Subject: [PATCH 2/8] Split image build and publish, gate publish on e2e --- .github/actions/use-local-image/action.yml | 7 +- .github/workflows/build.yml | 144 ++++++++++----------- 2 files changed, 69 insertions(+), 82 deletions(-) diff --git a/.github/actions/use-local-image/action.yml b/.github/actions/use-local-image/action.yml index 1e269211..b81cc509 100644 --- a/.github/actions/use-local-image/action.yml +++ b/.github/actions/use-local-image/action.yml @@ -1,8 +1,9 @@ name: 'Use local action image' description: |- Loads the action image built in this workflow run into a local registry - and points the checked-out action.yml to it. PR runs use this, because - they cannot push to ghcr.io (fork PRs get a read-only token). + and points the checked-out action.yml to it. The e2e tests use this, so + they can run before any image goes to ghcr.io (and on fork PRs, which get + a read-only token). inputs: path: @@ -36,7 +37,7 @@ runs: shell: bash run: | docker run -d --rm -p 5000:5000 --name action-release-registry \ - registry:2@sha256:a3d8aaa63ed8681a604f1dea0aa03f100d5895b6a58ace528858a7b332415373 + registry:3.1.2@sha256:d4032d4cc323c1731c707931ecada07a53076eac38e3b61ae57533f170f2c6c3 docker load -i .action-release-image/image.tar rm -rf .action-release-image # Retry until the registry is ready. A curl check does not work in diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e228f438..b78fecde 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -59,13 +59,14 @@ jobs: github-token: ${{ steps.token.outputs.token }} message: 'chore: Set docker tag for master [skip ci]' - docker-build-platform: - name: Build & publish Docker images - needs: prepare-docker - if: github.event_name == 'push' + # Builds the images without a push, so this job needs no write access. PRs + # (including fork PRs) and pushes use the same build. The e2e tests load the + # app image into a local registry, see `use-local-image`. + docker-build: + name: Build Docker images runs-on: ${{ matrix.platform.runner }} permissions: - packages: write + contents: read strategy: matrix: target: @@ -84,19 +85,10 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - with: - fetch-depth: 0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - - name: Login to GitHub Container Registry - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - # BUILDKIT_INLINE_CACHE creates the image in such a way that you can # then use --cache-from (think of a remote cache) # This feature is allowed thanks to using the buildx plugin @@ -104,19 +96,40 @@ jobs: # There's a COPY command in the builder stage that can easily invalidate the cache # If you notice, please add more exceptions to .dockerignore since we loose the value # of using --cache-from on the app stage - - name: Build and push + - name: Build uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 with: platforms: ${{ matrix.platform.name }} - push: true - tags: ghcr.io/${{ github.repository_owner }}/${{ matrix.target.image }}:${{ needs.prepare-docker.outputs.docker_tag }}-${{ matrix.platform.arch }} - cache-from: ghcr.io/${{ github.repository_owner }}/${{ matrix.target.image }}:master + tags: localhost:5000/${{ matrix.target.image }}:pr + outputs: type=docker,dest=${{ runner.temp }}/image.tar + # Inline cache only holds the final stage, so the app build also + # reads the builder image to reuse the builder stage. + cache-from: | + ghcr.io/${{ github.repository_owner }}/action-release-builder-image:master + ghcr.io/${{ github.repository_owner }}/action-release-image:master target: ${{ matrix.target.name }} build-args: BUILDKIT_INLINE_CACHE=1 - docker-build: - name: Merge multi-arch Docker images - needs: [prepare-docker, docker-build-platform] + - name: Upload image + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: ${{ matrix.target.image }}-${{ matrix.platform.arch }} + path: ${{ runner.temp }}/image.tar + retention-days: 1 + + # The only job that can write to ghcr.io. It runs only on push (reviewed code), + # after the e2e tests pass, and publishes the exact images they tested. + docker-publish: + name: Publish Docker images + needs: + - prepare-docker + - docker-build + - test-create-staging-release-per-push + - test-runs-on-container + - test-mock-release + - test-mock-release-working-directory + - test-node-version-preserved + - test-manual-commit-range if: github.event_name == 'push' runs-on: ubuntu-latest permissions: @@ -127,6 +140,12 @@ jobs: - action-release-builder-image - action-release-image steps: + - name: Download images + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + pattern: ${{ matrix.image }}-* + path: ${{ runner.temp }}/images + - name: Login to GitHub Container Registry uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 with: @@ -134,6 +153,17 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Push arch images + env: + LOCAL_IMAGE: localhost:5000/${{ matrix.image }}:pr + IMAGE: ghcr.io/${{ github.repository_owner }}/${{ matrix.image }}:${{ needs.prepare-docker.outputs.docker_tag }} + run: | + for ARCH in amd64 arm64; do + docker load -i "$RUNNER_TEMP/images/${{ matrix.image }}-$ARCH/image.tar" + docker tag "$LOCAL_IMAGE" "$IMAGE-$ARCH" + docker push "$IMAGE-$ARCH" + done + - name: Create and verify multi-arch manifest env: IMAGE: ghcr.io/${{ github.repository_owner }}/${{ matrix.image }}:${{ needs.prepare-docker.outputs.docker_tag }} @@ -144,46 +174,6 @@ jobs: grep -qx linux/amd64 <<< "$PLATFORMS" grep -qx linux/arm64 <<< "$PLATFORMS" - # PRs (including fork PRs, which get a read-only token) do not push to ghcr.io. - # The e2e tests load this image into a local registry, see `use-local-image`. - docker-build-pr: - name: Build Docker image for PR - if: github.event_name == 'pull_request' - runs-on: ${{ matrix.platform.runner }} - permissions: - contents: read - strategy: - matrix: - platform: - - name: linux/amd64 - arch: amd64 - runner: ubuntu-latest - - name: linux/arm64 - arch: arm64 - runner: ubuntu-24.04-arm - steps: - - name: Checkout repo - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - - - name: Build - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 - with: - platforms: ${{ matrix.platform.name }} - tags: localhost:5000/action-release-image:pr - outputs: type=docker,dest=${{ runner.temp }}/image.tar - cache-from: ghcr.io/${{ github.repository_owner }}/action-release-image:master - target: app - - - name: Upload image - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 - with: - name: action-release-image-${{ matrix.platform.arch }} - path: ${{ runner.temp }}/image.tar - retention-days: 1 - lint: runs-on: ubuntu-latest permissions: @@ -209,9 +199,9 @@ jobs: ############# test-create-staging-release-per-push: - needs: [docker-build, docker-build-pr] + needs: docker-build # Needs SENTRY_AUTH_TOKEN, which fork PRs do not get. - if: ${{ !failure() && !cancelled() && !github.event.pull_request.head.repo.fork }} + if: ${{ !github.event.pull_request.head.repo.fork }} strategy: matrix: os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-latest] @@ -225,7 +215,7 @@ jobs: fetch-depth: 0 - name: Use local action image - if: github.event_name == 'pull_request' && runner.os == 'Linux' + if: runner.os == 'Linux' uses: ./.github/actions/use-local-image - name: Create a staging release @@ -237,9 +227,9 @@ jobs: ignore_missing: true test-runs-on-container: - needs: [docker-build, docker-build-pr] + needs: docker-build # Needs SENTRY_AUTH_TOKEN, which fork PRs do not get. - if: ${{ !failure() && !cancelled() && !github.event.pull_request.head.repo.fork }} + if: ${{ !github.event.pull_request.head.repo.fork }} runs-on: ubuntu-latest permissions: contents: read @@ -252,7 +242,7 @@ jobs: fetch-depth: 0 - name: Use local action image - if: github.event_name == 'pull_request' && runner.os == 'Linux' + if: runner.os == 'Linux' uses: ./.github/actions/use-local-image - name: Create a staging release @@ -264,8 +254,7 @@ jobs: ignore_missing: true test-mock-release: - needs: [docker-build, docker-build-pr] - if: ${{ !failure() && !cancelled() }} + needs: docker-build strategy: matrix: os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-latest] @@ -279,7 +268,7 @@ jobs: fetch-depth: 0 - name: Use local action image - if: github.event_name == 'pull_request' && runner.os == 'Linux' + if: runner.os == 'Linux' uses: ./.github/actions/use-local-image - name: Mock creating a Sentry release @@ -290,8 +279,7 @@ jobs: environment: production test-mock-release-working-directory: - needs: [docker-build, docker-build-pr] - if: ${{ !failure() && !cancelled() }} + needs: docker-build strategy: matrix: os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-latest] @@ -313,7 +301,7 @@ jobs: path: test/ - name: Use local action image - if: github.event_name == 'pull_request' && runner.os == 'Linux' + if: runner.os == 'Linux' uses: ./main/.github/actions/use-local-image with: path: main @@ -327,8 +315,7 @@ jobs: working_directory: ./test test-node-version-preserved: - needs: [docker-build, docker-build-pr] - if: ${{ !failure() && !cancelled() }} + needs: docker-build strategy: matrix: os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-latest] @@ -343,7 +330,7 @@ jobs: fetch-depth: 0 - name: Use local action image - if: github.event_name == 'pull_request' && runner.os == 'Linux' + if: runner.os == 'Linux' uses: ./.github/actions/use-local-image - name: Setup Node @@ -380,8 +367,7 @@ jobs: echo "SUCCESS: Node version preserved" test-manual-commit-range: - needs: [docker-build, docker-build-pr] - if: ${{ !failure() && !cancelled() }} + needs: docker-build strategy: matrix: @@ -396,7 +382,7 @@ jobs: fetch-depth: 0 - name: Use local action image - if: github.event_name == 'pull_request' && runner.os == 'Linux' + if: runner.os == 'Linux' uses: ./.github/actions/use-local-image - name: Create a release with manual commit range From e7d10337d4d0b5422365788c0548184784c51201 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Thu, 29 Oct 2026 14:17:09 +0100 Subject: [PATCH 3/8] Add comment to replacement check --- .github/actions/use-local-image/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/use-local-image/action.yml b/.github/actions/use-local-image/action.yml index b81cc509..0836bb7f 100644 --- a/.github/actions/use-local-image/action.yml +++ b/.github/actions/use-local-image/action.yml @@ -53,4 +53,5 @@ runs: working-directory: ${{ inputs.path }} run: | sed -i -e 's|docker://ghcr.io/getsentry/action-release-image:.*|docker://localhost:5000/action-release-image:pr|' action.yml + # Make sure the replacement worked grep -q 'docker://localhost:5000/action-release-image:pr' action.yml From fa22c538a45667d337697ad462dba9c37e03cd29 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Thu, 29 Oct 2026 14:40:07 +0100 Subject: [PATCH 4/8] Document local image tag and testing in a different repo --- .github/workflows/build.yml | 2 ++ docs/development.md | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b78fecde..faabd18c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -100,6 +100,8 @@ jobs: uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 with: platforms: ${{ matrix.platform.name }} + # Local name inside the tar only, also on master and release runs. It is never + # pushed to ghcr.io. `docker-publish` retags the image before it pushes it. tags: localhost:5000/${{ matrix.target.image }}:pr outputs: type=docker,dest=${{ runner.temp }}/image.tar # Inline cache only holds the final stage, so the app build also diff --git a/docs/development.md b/docs/development.md index a007eed6..8b140ff8 100644 --- a/docs/development.md +++ b/docs/development.md @@ -59,7 +59,13 @@ Members of this repo will not have to set anything up since [the integration](ht Do not change the Docker image tag in `action.yml`. PR builds do not publish an image to ghcr.io. The E2E tests load the image of the PR into a local registry (see `.github/actions/use-local-image`). -To use your changes from a different repo, merge them and use `getsentry/action-release@master`. + +### Testing in a different repo + +- After the PR is merged, use `getsentry/action-release@master`. CI publishes the `master` image after the E2E tests pass. +- Before the PR is merged, use `getsentry/action-release@` on a macOS or Windows runner. + These runners do not use the Docker image. They run `dist/` from your branch. + Linux runners use the Docker image, and CI does not publish an image for PR branches. ## Testing From 6921d0e7937e8074f02d81f546b2c6fc928a8840 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Thu, 29 Oct 2026 14:46:18 +0100 Subject: [PATCH 5/8] Move e2e tests to a reusable workflow --- .github/workflows/build.yml | 224 +----------------------------------- .github/workflows/e2e.yml | 210 +++++++++++++++++++++++++++++++++ docs/development.md | 2 +- 3 files changed, 217 insertions(+), 219 deletions(-) create mode 100644 .github/workflows/e2e.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index faabd18c..aae69073 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,15 +11,6 @@ on: paths-ignore: - '**.md' -env: - # Variables defined in the repository - SENTRY_ORG: ${{ vars.SENTRY_ORG }} - # For master, we have an environment variable that selects the action-release project - # instead of action-release-prs - # For other branches: https://sentry-ecosystem.sentry.io/releases/?project=4505075304693760 - # For master branch: https://sentry-ecosystem.sentry.io/releases/?project=6576594 - SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT }} - jobs: prepare-docker: name: Prepare docker tag @@ -123,15 +114,7 @@ jobs: # after the e2e tests pass, and publishes the exact images they tested. docker-publish: name: Publish Docker images - needs: - - prepare-docker - - docker-build - - test-create-staging-release-per-push - - test-runs-on-container - - test-mock-release - - test-mock-release-working-directory - - test-node-version-preserved - - test-manual-commit-range + needs: [prepare-docker, e2e] if: github.event_name == 'push' runs-on: ubuntu-latest permissions: @@ -196,206 +179,11 @@ jobs: - name: Build run: yarn build - ############# - # E2E Tests - ############# - - test-create-staging-release-per-push: + # All E2E tests are in e2e.yml. Add new tests there, so they also gate docker-publish. + e2e: + name: E2E needs: docker-build - # Needs SENTRY_AUTH_TOKEN, which fork PRs do not get. - if: ${{ !github.event.pull_request.head.repo.fork }} - strategy: - matrix: - os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-latest] - runs-on: ${{ matrix.os }} + uses: ./.github/workflows/e2e.yml + secrets: inherit permissions: contents: read - name: Test current action - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - with: - fetch-depth: 0 - - - name: Use local action image - if: runner.os == 'Linux' - uses: ./.github/actions/use-local-image - - - name: Create a staging release - uses: ./ - env: - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - SENTRY_LOG_LEVEL: debug - with: - ignore_missing: true - - test-runs-on-container: - needs: docker-build - # Needs SENTRY_AUTH_TOKEN, which fork PRs do not get. - if: ${{ !github.event.pull_request.head.repo.fork }} - runs-on: ubuntu-latest - permissions: - contents: read - container: - image: node:20.19.2 - - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - with: - fetch-depth: 0 - - - name: Use local action image - if: runner.os == 'Linux' - uses: ./.github/actions/use-local-image - - - name: Create a staging release - uses: ./ - env: - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - SENTRY_LOG_LEVEL: debug - with: - ignore_missing: true - - test-mock-release: - needs: docker-build - strategy: - matrix: - os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-latest] - runs-on: ${{ matrix.os }} - name: Mock a release - permissions: - contents: read - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - with: - fetch-depth: 0 - - - name: Use local action image - if: runner.os == 'Linux' - uses: ./.github/actions/use-local-image - - - name: Mock creating a Sentry release - uses: ./ - env: - MOCK: true - with: - environment: production - - test-mock-release-working-directory: - needs: docker-build - strategy: - matrix: - os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-latest] - runs-on: ${{ matrix.os }} - name: Mock a release in a different working directory - permissions: - contents: read - steps: - - name: Checkout directory we'll be running from - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - with: - fetch-depth: 0 - path: main/ - - - name: Checkout directory we'll be testing - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - with: - fetch-depth: 0 - path: test/ - - - name: Use local action image - if: runner.os == 'Linux' - uses: ./main/.github/actions/use-local-image - with: - path: main - - - name: Mock creating a Sentry release in a different directory - uses: ./main - env: - MOCK: true - with: - environment: production - working_directory: ./test - - test-node-version-preserved: - needs: docker-build - strategy: - matrix: - os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-latest] - node-version: ['20.x', '22.x', '24.x'] - runs-on: ${{ matrix.os }} - name: Test Node version preserved on ${{ matrix.os }} with Node ${{ matrix.node-version }} - permissions: - contents: read - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - with: - fetch-depth: 0 - - - name: Use local action image - if: runner.os == 'Linux' - uses: ./.github/actions/use-local-image - - - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ matrix.node-version }} - - - name: Print Node Version (Before) - id: node_before - shell: bash - run: | - VERSION=$(node --version) - echo "Node version before: $VERSION" - echo "VERSION=$VERSION" >> $GITHUB_OUTPUT - - - name: Mock creating a Sentry release - uses: ./ - env: - MOCK: true - with: - environment: production - - - name: Print Node Version (After) - shell: bash - run: | - VERSION_AFTER=$(node --version) - echo "Node version after: $VERSION_AFTER" - echo "Expected: ${{ steps.node_before.outputs.VERSION }}" - if [ "$VERSION_AFTER" != "${{ steps.node_before.outputs.VERSION }}" ]; then - echo "ERROR: Node version changed from ${{ steps.node_before.outputs.VERSION }} to $VERSION_AFTER" - exit 1 - fi - - echo "SUCCESS: Node version preserved" - - test-manual-commit-range: - needs: docker-build - - strategy: - matrix: - os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-latest] - runs-on: ${{ matrix.os }} - name: Test manual commit range - permissions: - contents: read - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - with: - fetch-depth: 0 - - - name: Use local action image - if: runner.os == 'Linux' - uses: ./.github/actions/use-local-image - - - name: Create a release with manual commit range - uses: ./ - env: - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - SENTRY_LOG_LEVEL: debug - MOCK: true - with: - environment: production - set_commits: manual - repo: getsentry/action-release - commit: ${{ github.sha }} - previous_commit: ${{ github.sha }} diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml new file mode 100644 index 00000000..b4757f18 --- /dev/null +++ b/.github/workflows/e2e.yml @@ -0,0 +1,210 @@ +name: E2E Tests + +# Called from build.yml after the images are built. The `e2e` job there passes +# only if all jobs here pass, and docker-publish waits for it. +on: + workflow_call: + +env: + # Variables defined in the repository + SENTRY_ORG: ${{ vars.SENTRY_ORG }} + # For master, we have an environment variable that selects the action-release project + # instead of action-release-prs + # For other branches: https://sentry-ecosystem.sentry.io/releases/?project=4505075304693760 + # For master branch: https://sentry-ecosystem.sentry.io/releases/?project=6576594 + SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT }} + +jobs: + test-create-staging-release-per-push: + # Needs SENTRY_AUTH_TOKEN, which fork PRs do not get. + if: ${{ !github.event.pull_request.head.repo.fork }} + strategy: + matrix: + os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-latest] + runs-on: ${{ matrix.os }} + permissions: + contents: read + name: Test current action + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + fetch-depth: 0 + + - name: Use local action image + if: runner.os == 'Linux' + uses: ./.github/actions/use-local-image + + - name: Create a staging release + uses: ./ + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_LOG_LEVEL: debug + with: + ignore_missing: true + + test-runs-on-container: + # Needs SENTRY_AUTH_TOKEN, which fork PRs do not get. + if: ${{ !github.event.pull_request.head.repo.fork }} + runs-on: ubuntu-latest + permissions: + contents: read + container: + image: node:20.19.2 + + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + fetch-depth: 0 + + - name: Use local action image + if: runner.os == 'Linux' + uses: ./.github/actions/use-local-image + + - name: Create a staging release + uses: ./ + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_LOG_LEVEL: debug + with: + ignore_missing: true + + test-mock-release: + strategy: + matrix: + os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-latest] + runs-on: ${{ matrix.os }} + name: Mock a release + permissions: + contents: read + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + fetch-depth: 0 + + - name: Use local action image + if: runner.os == 'Linux' + uses: ./.github/actions/use-local-image + + - name: Mock creating a Sentry release + uses: ./ + env: + MOCK: true + with: + environment: production + + test-mock-release-working-directory: + strategy: + matrix: + os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-latest] + runs-on: ${{ matrix.os }} + name: Mock a release in a different working directory + permissions: + contents: read + steps: + - name: Checkout directory we'll be running from + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + fetch-depth: 0 + path: main/ + + - name: Checkout directory we'll be testing + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + fetch-depth: 0 + path: test/ + + - name: Use local action image + if: runner.os == 'Linux' + uses: ./main/.github/actions/use-local-image + with: + path: main + + - name: Mock creating a Sentry release in a different directory + uses: ./main + env: + MOCK: true + with: + environment: production + working_directory: ./test + + test-node-version-preserved: + strategy: + matrix: + os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-latest] + node-version: ['20.x', '22.x', '24.x'] + runs-on: ${{ matrix.os }} + name: Test Node version preserved on ${{ matrix.os }} with Node ${{ matrix.node-version }} + permissions: + contents: read + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + fetch-depth: 0 + + - name: Use local action image + if: runner.os == 'Linux' + uses: ./.github/actions/use-local-image + + - name: Setup Node + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version: ${{ matrix.node-version }} + + - name: Print Node Version (Before) + id: node_before + shell: bash + run: | + VERSION=$(node --version) + echo "Node version before: $VERSION" + echo "VERSION=$VERSION" >> $GITHUB_OUTPUT + + - name: Mock creating a Sentry release + uses: ./ + env: + MOCK: true + with: + environment: production + + - name: Print Node Version (After) + shell: bash + run: | + VERSION_AFTER=$(node --version) + echo "Node version after: $VERSION_AFTER" + echo "Expected: ${{ steps.node_before.outputs.VERSION }}" + if [ "$VERSION_AFTER" != "${{ steps.node_before.outputs.VERSION }}" ]; then + echo "ERROR: Node version changed from ${{ steps.node_before.outputs.VERSION }} to $VERSION_AFTER" + exit 1 + fi + + echo "SUCCESS: Node version preserved" + + test-manual-commit-range: + + strategy: + matrix: + os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-latest] + runs-on: ${{ matrix.os }} + name: Test manual commit range + permissions: + contents: read + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + fetch-depth: 0 + + - name: Use local action image + if: runner.os == 'Linux' + uses: ./.github/actions/use-local-image + + - name: Create a release with manual commit range + uses: ./ + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_LOG_LEVEL: debug + MOCK: true + with: + environment: production + set_commits: manual + repo: getsentry/action-release + commit: ${{ github.sha }} + previous_commit: ${{ github.sha }} diff --git a/docs/development.md b/docs/development.md index 8b140ff8..a88dc967 100644 --- a/docs/development.md +++ b/docs/development.md @@ -52,7 +52,7 @@ Members of this repo will not have to set anything up since [the integration](ht 1. Create a branch 2. Make changes -3. If possible, add unit and E2E tests (inside `.github/workflows/build.yml`) +3. If possible, add unit and E2E tests (E2E tests go in `.github/workflows/e2e.yml`) 4. Run `yarn install` to install deps 5. Run `yarn build` to build the action 6. Commit the changes and the build inside `dist/` From 0c838eb917e999ef73e2bbb4b7aef0aa26432b8f Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Thu, 29 Oct 2026 15:32:55 +0100 Subject: [PATCH 6/8] Keep provenance and push exact bytes with skopeo --- .github/workflows/build.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index aae69073..8eb93051 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -92,9 +92,11 @@ jobs: with: platforms: ${{ matrix.platform.name }} # Local name inside the tar only, also on master and release runs. It is never - # pushed to ghcr.io. `docker-publish` retags the image before it pushes it. + # pushed to ghcr.io. `docker-publish` pushes the image under its ghcr.io name. tags: localhost:5000/${{ matrix.target.image }}:pr - outputs: type=docker,dest=${{ runner.temp }}/image.tar + # OCI archive, so it keeps the provenance attestation and `docker-publish` + # can push the exact bytes that the e2e tests used. + outputs: type=oci,dest=${{ runner.temp }}/image.tar # Inline cache only holds the final stage, so the app build also # reads the builder image to reuse the builder stage. cache-from: | @@ -138,15 +140,16 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + # skopeo reads the credentials of `docker login`. `--preserve-digests` fails + # the push if the image would change, so ghcr.io gets the tested bytes. - name: Push arch images env: - LOCAL_IMAGE: localhost:5000/${{ matrix.image }}:pr IMAGE: ghcr.io/${{ github.repository_owner }}/${{ matrix.image }}:${{ needs.prepare-docker.outputs.docker_tag }} run: | for ARCH in amd64 arm64; do - docker load -i "$RUNNER_TEMP/images/${{ matrix.image }}-$ARCH/image.tar" - docker tag "$LOCAL_IMAGE" "$IMAGE-$ARCH" - docker push "$IMAGE-$ARCH" + skopeo copy --all --preserve-digests \ + "oci-archive:$RUNNER_TEMP/images/${{ matrix.image }}-$ARCH/image.tar" \ + "docker://$IMAGE-$ARCH" done - name: Create and verify multi-arch manifest From 3cc3d45d0b92aa2553d531d496e3dcccac8b1995 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Thu, 29 Oct 2026 15:51:03 +0100 Subject: [PATCH 7/8] Pin Ubuntu 26.04 runners and set Sentry env fallbacks for forks --- .github/workflows/build.yml | 4 ++-- .github/workflows/e2e.yml | 21 +++++++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8eb93051..05e08273 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -69,10 +69,10 @@ jobs: platform: - name: linux/amd64 arch: amd64 - runner: ubuntu-latest + runner: ubuntu-26.04 - name: linux/arm64 arch: arm64 - runner: ubuntu-24.04-arm + runner: ubuntu-26.04-arm steps: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index b4757f18..b4f96f18 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -5,14 +5,18 @@ name: E2E Tests on: workflow_call: +# Linux jobs use Ubuntu 26.04 runners (Docker 29). `use-local-image` loads an OCI +# archive, and the Docker 28 on Ubuntu 24.04 runners cannot load it. + env: # Variables defined in the repository - SENTRY_ORG: ${{ vars.SENTRY_ORG }} + # Fork PRs do not get repository variables, so fall back to the values for PRs. + SENTRY_ORG: ${{ vars.SENTRY_ORG || 'sentry-ecosystem' }} # For master, we have an environment variable that selects the action-release project # instead of action-release-prs # For other branches: https://sentry-ecosystem.sentry.io/releases/?project=4505075304693760 # For master branch: https://sentry-ecosystem.sentry.io/releases/?project=6576594 - SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT }} + SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT || 'action-release-prs' }} jobs: test-create-staging-release-per-push: @@ -20,7 +24,7 @@ jobs: if: ${{ !github.event.pull_request.head.repo.fork }} strategy: matrix: - os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-latest] + os: [ubuntu-26.04, ubuntu-26.04-arm, windows-latest, macos-latest] runs-on: ${{ matrix.os }} permissions: contents: read @@ -45,7 +49,8 @@ jobs: test-runs-on-container: # Needs SENTRY_AUTH_TOKEN, which fork PRs do not get. if: ${{ !github.event.pull_request.head.repo.fork }} - runs-on: ubuntu-latest + # Pinned like the Linux jobs above, see the comment at the top of this file. + runs-on: ubuntu-26.04 permissions: contents: read container: @@ -71,7 +76,7 @@ jobs: test-mock-release: strategy: matrix: - os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-latest] + os: [ubuntu-26.04, ubuntu-26.04-arm, windows-latest, macos-latest] runs-on: ${{ matrix.os }} name: Mock a release permissions: @@ -95,7 +100,7 @@ jobs: test-mock-release-working-directory: strategy: matrix: - os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-latest] + os: [ubuntu-26.04, ubuntu-26.04-arm, windows-latest, macos-latest] runs-on: ${{ matrix.os }} name: Mock a release in a different working directory permissions: @@ -130,7 +135,7 @@ jobs: test-node-version-preserved: strategy: matrix: - os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-latest] + os: [ubuntu-26.04, ubuntu-26.04-arm, windows-latest, macos-latest] node-version: ['20.x', '22.x', '24.x'] runs-on: ${{ matrix.os }} name: Test Node version preserved on ${{ matrix.os }} with Node ${{ matrix.node-version }} @@ -182,7 +187,7 @@ jobs: strategy: matrix: - os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-latest] + os: [ubuntu-26.04, ubuntu-26.04-arm, windows-latest, macos-latest] runs-on: ${{ matrix.os }} name: Test manual commit range permissions: From c1bd1aa5de1fd3cba832e3e064e6e2c366ec0e35 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Thu, 29 Oct 2026 16:21:53 +0100 Subject: [PATCH 8/8] Let MOCK fill the auth token in the manual commit range test --- .github/workflows/e2e.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index b4f96f18..5743594d 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -204,7 +204,6 @@ jobs: - name: Create a release with manual commit range uses: ./ env: - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_LOG_LEVEL: debug MOCK: true with: