diff --git a/.github/workflows/cut-prerelease.yaml b/.github/workflows/cut-prerelease.yaml index c3c31450f..e740c2978 100644 --- a/.github/workflows/cut-prerelease.yaml +++ b/.github/workflows/cut-prerelease.yaml @@ -48,8 +48,7 @@ concurrency: group: cut-prerelease cancel-in-progress: false -# The tag write happens via the app token, not GITHUB_TOKEN, so the job needs -# only read scope on the default token. +# Tag and branch writes use the app token so their push events trigger CI. permissions: contents: read @@ -58,6 +57,9 @@ jobs: name: Cut ${{ inputs.tag }} runs-on: ubuntu-latest timeout-minutes: 10 + permissions: + contents: read + actions: write steps: # Pure-input validation first — no token minted, no checkout, so a bad tag @@ -345,13 +347,14 @@ jobs: - name: Build the frozen line's packages artifact if: steps.freeze.outputs.created == 'true' env: - GH_TOKEN: ${{ steps.app-token.outputs.token }} + # workflow_dispatch can trigger a run with GITHUB_TOKEN. + GH_TOKEN: ${{ github.token }} BRANCH: release-${{ steps.parse.outputs.line }} run: | if gh workflow run build-release.yaml --ref "$BRANCH"; then echo "Dispatched build-release.yaml for $BRANCH; it publishes cozystack-packages:$BRANCH." else - echo "::warning::Could not dispatch build-release.yaml for $BRANCH (the CI app may lack 'actions: write')." + echo "::warning::Could not dispatch build-release.yaml for $BRANCH." echo "::warning::Until it runs, cozystack-packages:$BRANCH does not exist, and PRs onto $BRANCH test the image refs committed in the tree rather than the line's own build." echo "::warning::Start it by hand: gh workflow run build-release.yaml --ref $BRANCH" fi diff --git a/hack/release-freeze-contract.bats b/hack/release-freeze-contract.bats index 30a6e20bb..197734a2f 100644 --- a/hack/release-freeze-contract.bats +++ b/hack/release-freeze-contract.bats @@ -204,6 +204,39 @@ step_line() { [ "${count:-0}" -eq 1 ] } +@test "initial line build dispatch has Actions access and preserves failure recovery" { + step="$(STEP_NAME="Build the frozen line's packages artifact" yq -o=json '.jobs.cut.steps[] | select(.name == strenv(STEP_NAME))' "$CUT")" + script="$(printf '%s\n' "$step" | yq -r '.run')" + token="$(printf '%s\n' "$step" | yq -r '.env.GH_TOKEN')" + [ -n "$script" ] && [ "$script" != null ] + case "$token" in + '${{ github.token }}'|'${{ secrets.GITHUB_TOKEN }}') token=workflow-token ;; + '${{ steps.app-token.outputs.token }}') token=app-token ;; + *) echo "Unknown dispatch credential: $token"; return 1 ;; + esac + actions="$(yq -r '(.jobs.cut.permissions // .permissions).actions // "none"' "$CUT")" + + # The installed CI app has no Actions permission; the workflow token only + # gets it when the effective job permissions request it. + gh() { + [ "$*" = 'workflow run build-release.yaml --ref release-1.7' ] || return 2 + [ "$GH_TOKEN" = workflow-token ] && [ "$ACTIONS_PERMISSION" = write ] || return 1 + return "$DISPATCH_STATUS" + } + export -f gh + + run env GH_TOKEN="$token" ACTIONS_PERMISSION="$actions" BRANCH=release-1.7 \ + DISPATCH_STATUS=0 bash -e -c "$script" + [ "$status" -eq 0 ] + [[ "$output" == *'Dispatched build-release.yaml for release-1.7;'* ]] + + run env GH_TOKEN="$token" ACTIONS_PERMISSION="$actions" BRANCH=release-1.7 \ + DISPATCH_STATUS=1 bash -e -c "$script" + [ "$status" -eq 0 ] + [[ "$output" == *'::warning::Could not dispatch build-release.yaml for release-1.7'* ]] + [[ "$output" == *'Start it by hand: gh workflow run build-release.yaml --ref release-1.7'* ]] +} + @test "build-release refuses a dispatch from anything but a line branch" { BUILD_RELEASE="$REPO_ROOT/.github/workflows/build-release.yaml" [ -f "$BUILD_RELEASE" ]