From d0b57b4b613c9830dcbae83fe212125bcc0e2eae Mon Sep 17 00:00:00 2001 From: Caue Santos Date: Thu, 1 Oct 2026 10:26:54 -0600 Subject: [PATCH] test(e2e-command): cover fork commands in the platform harness --- .github/actionlint.yaml | 4 +++ .../workflows/caller-platform-command.yaml | 36 ++++++++++++++++--- .github/workflows/caller-platform-run.yaml | 31 +++++++++++++--- repro/fork-command/README.md | 8 +++-- repro/fork-command/run.sh | 13 ++++++- 5 files changed, 79 insertions(+), 13 deletions(-) diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml index e2a97ad..7855f92 100644 --- a/.github/actionlint.yaml +++ b/.github/actionlint.yaml @@ -11,3 +11,7 @@ paths: ignore: - 'unexpected key "cache-mode" for "workflow" section' - 'cannot save caches with effective cache-mode "read"' + .github/workflows/caller-platform-run.yaml: + ignore: + - 'unexpected key "cache-mode" for "workflow" section' + - 'cannot save caches with effective cache-mode "read"' diff --git a/.github/workflows/caller-platform-command.yaml b/.github/workflows/caller-platform-command.yaml index 5f58590..c3c4696 100644 --- a/.github/workflows/caller-platform-command.yaml +++ b/.github/workflows/caller-platform-command.yaml @@ -18,10 +18,12 @@ jobs: checks: write pull-requests: write outputs: - should-run: ${{ steps.command.outputs.should-run }} + should-run: ${{ steps.command.outputs.should-run == 'true' && steps.contract.outcome == 'success' }} filter: ${{ steps.command.outputs.filter }} focus: ${{ steps.command.outputs.focus }} + is-fork: ${{ steps.command.outputs.is-fork }} head-ref: ${{ steps.command.outputs.head-ref }} + base-ref: ${{ steps.command.outputs.base-ref }} head-sha: ${{ steps.command.outputs.head-sha }} key: ${{ steps.command.outputs.concurrency-key }} check-run-id: ${{ steps.command.outputs.check-run-id }} @@ -29,7 +31,7 @@ jobs: steps: - name: Parse command and open check id: command - uses: loft-sh/github-actions/.github/actions/comment-triggered-check@comment-triggered-check/v1 + uses: loft-sh/github-actions/.github/actions/comment-triggered-check@80d63f2a76ef1b6bd2e4d2e07cdd465c04594303 # github-actions#276 head until comment-triggered-check/v1 moves with: mode: start command: /test-platform-e2e @@ -38,10 +40,32 @@ jobs: comment-body: ${{ github.event.comment.body }} comment-author: ${{ github.event.comment.user.login }} author-association: ${{ github.event.comment.author_association }} + allow-forks: true pr-number: ${{ github.event.issue.number }} run-id: ${{ github.run_id }} server-url: ${{ github.server_url }} + # The contract lives on the workflow that will run: the PR head for + # same-repository PRs, the target branch for forks. + - name: Verify the branch supports command dispatch + id: contract + if: ${{ steps.command.outputs.should-run == 'true' }} + continue-on-error: true + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CONTRACT_REF: ${{ steps.command.outputs.is-fork == 'true' && steps.command.outputs.base-ref || steps.command.outputs.head-sha }} + run: gh api "repos/${GITHUB_REPOSITORY}/contents/.github/e2e-command-contract-v2?ref=${CONTRACT_REF}" >/dev/null + + - name: Close the check if the branch has no contract + if: ${{ always() && steps.command.outputs.should-run == 'true' && steps.contract.outcome != 'success' }} + uses: loft-sh/github-actions/.github/actions/comment-triggered-check@80d63f2a76ef1b6bd2e4d2e07cdd465c04594303 # github-actions#276 head until comment-triggered-check/v1 moves + with: + mode: finish + check-run-id: ${{ steps.command.outputs.check-run-id }} + report-conclusion: neutral + summary: This branch does not support /test-platform-e2e. + details-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + - name: Explain refusal if: ${{ steps.command.outputs.matched == 'true' && steps.command.outputs.should-run != 'true' }} uses: loft-sh/github-actions/.github/actions/sticky-pr-comment@sticky-pr-comment/v1 @@ -64,7 +88,9 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_REPO: ${{ github.repository }} - HEAD_REF: ${{ needs.prepare.outputs.head-ref }} + # Same-repository PRs run their branch's workflow. Forks use the target + # branch's workflow because their branch does not exist in this repo. + WORKFLOW_REF: ${{ needs.prepare.outputs.is-fork == 'true' && needs.prepare.outputs.base-ref || needs.prepare.outputs.head-ref }} HEAD_SHA: ${{ needs.prepare.outputs.head-sha }} FILTER: ${{ needs.prepare.outputs.filter }} FOCUS: ${{ needs.prepare.outputs.focus }} @@ -78,7 +104,7 @@ jobs: continue-on-error: true run: | gh workflow run caller-platform-run.yaml \ - --ref "$HEAD_REF" \ + --ref "$WORKFLOW_REF" \ -f "ginkgo-label=$FILTER" \ -f "ginkgo-focus=$FOCUS" \ -f "head-sha=$HEAD_SHA" \ @@ -88,7 +114,7 @@ jobs: - name: Close check if dispatch failed if: ${{ always() && steps.dispatch.outcome == 'failure' }} - uses: loft-sh/github-actions/.github/actions/comment-triggered-check@comment-triggered-check/v1 + uses: loft-sh/github-actions/.github/actions/comment-triggered-check@80d63f2a76ef1b6bd2e4d2e07cdd465c04594303 # github-actions#276 head until comment-triggered-check/v1 moves with: mode: finish check-run-id: ${{ needs.prepare.outputs.check-run-id }} diff --git a/.github/workflows/caller-platform-run.yaml b/.github/workflows/caller-platform-run.yaml index 511750e..8fa0de2 100644 --- a/.github/workflows/caller-platform-run.yaml +++ b/.github/workflows/caller-platform-run.yaml @@ -30,6 +30,9 @@ on: permissions: {} +# A fork run can restore caches but never save them. +cache-mode: read + jobs: suite: name: Mock Platform suite @@ -41,22 +44,42 @@ jobs: outputs: check-conclusion: ${{ steps.conclusion.outputs.check-conclusion }} steps: + # The workflow comes from a trusted branch, the code from the captured SHA. + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ inputs.head-sha }} + persist-credentials: false + - name: Verify head SHA env: EXPECTED_HEAD_SHA: ${{ inputs.head-sha }} - ACTUAL_HEAD_SHA: ${{ github.sha }} run: | - [[ "$EXPECTED_HEAD_SHA" == "$ACTUAL_HEAD_SHA" ]] || exit 1 + ACTUAL_HEAD_SHA="$(git rev-parse HEAD)" + if [[ "$EXPECTED_HEAD_SHA" != "$ACTUAL_HEAD_SHA" ]]; then + echo "::error::expected ${EXPECTED_HEAD_SHA}, checked out ${ACTUAL_HEAD_SHA}" + exit 1 + fi - name: Simulate suite env: FILTER: ${{ inputs.ginkgo-label }} FOCUS: ${{ inputs.ginkgo-focus }} run: | - sleep 20 + echo "tested commit: $(git log -1 --format='%H %s')" + if [[ -f fork-marker.txt ]]; then echo "fork marker: $(cat fork-marker.txt)"; fi + sleep 60 printf 'filter=%s\nfocus=%s\n' "$FILTER" "$FOCUS" [[ "$FILTER" != *fail* ]] || exit 1 + # Expected to be skipped by cache-mode: read. repro/fork-command checks + # afterwards that no cache with this key exists. + - name: Try to save a cache + continue-on-error: true + uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: fork-marker.txt + key: fork-cache-probe-platform-${{ github.run_id }} + - name: Derive result id: conclusion if: ${{ !cancelled() }} @@ -81,7 +104,7 @@ jobs: checks: write steps: - name: Complete check - uses: loft-sh/github-actions/.github/actions/comment-triggered-check@comment-triggered-check/v1 + uses: loft-sh/github-actions/.github/actions/comment-triggered-check@80d63f2a76ef1b6bd2e4d2e07cdd465c04594303 # github-actions#276 head until comment-triggered-check/v1 moves with: mode: finish check-run-id: ${{ inputs.check-run-id }} diff --git a/repro/fork-command/README.md b/repro/fork-command/README.md index 508b210..9b1cedc 100644 --- a/repro/fork-command/README.md +++ b/repro/fork-command/README.md @@ -1,7 +1,7 @@ # Fork `/test-e2e` scenarios Live checks for running `/test-e2e` on pull requests from forks (DEVOPS-1541). -The caller workflows here mirror `vcluster-pro#2478` with stand-in suites: +The caller workflows here mirror `vcluster-pro#2478` (two trees) and the matching loft-enterprise change (`caller-platform-*`, one tree) with stand-in suites: - Fork commands dispatch the target branch's workflow and check out the PR SHA captured when the comment was posted. @@ -10,7 +10,7 @@ The caller workflows here mirror `vcluster-pro#2478` with stand-in suites: - The contract check runs on the workflow that will run, so a branch without `.github/e2e-command-contract-v2` is refused before dispatch. -`caller-comment-triggered-check.yaml` and both suite runs pin +The command callers (`caller-comment-triggered-check.yaml`, `caller-platform-command.yaml`) and their suite runs pin `comment-triggered-check` to the `loft-sh/github-actions#276` head. Move them back to `@comment-triggered-check/v1` once that tag is advanced. @@ -35,7 +35,9 @@ It leaves `release-no-contract` in place for the next run. | 4 | Push to the fork branch after commenting | The check on the commented commit succeeds. The new commit gets none | | 5 | Same-repository PR | Both trees succeed | | 6 | Fork PR into `release-no-contract` | Both checks are `neutral` without a dispatch | -| 7 | After the runs | No `fork-cache-probe` cache exists | +| 7 | `/test-platform-e2e` on two fork PRs (single-check harness, mirrors loft-enterprise) | Both succeed | +| 8 | `/test-platform-e2e` on the fork PR into `release-no-contract` | `neutral` without a dispatch | +| 9 | After the runs | No `fork-cache-probe` cache exists | A commenter without write access is not scripted, because it needs a second account. Check it by hand: the command should reply with `insufficient-permission`. diff --git a/repro/fork-command/run.sh b/repro/fork-command/run.sh index 8a4370d..fa14f78 100755 --- a/repro/fork-command/run.sh +++ b/repro/fork-command/run.sh @@ -110,6 +110,10 @@ gh pr comment "$PR_A" -R "$UPSTREAM" --body "/test-e2e fork-repeat --target pro" gh pr comment "$PR_PUSH" -R "$UPSTREAM" --body "/test-e2e fork-push --target oss" >/dev/null gh pr comment "$PR_SAME" -R "$UPSTREAM" --body "/test-e2e same-repo" >/dev/null gh pr comment "$PR_OLD" -R "$UPSTREAM" --body "/test-e2e fork-old" >/dev/null +# The single-check Platform harness, mirroring loft-enterprise. +gh pr comment "$PR_A" -R "$UPSTREAM" --body "/test-platform-e2e platform-smoke" >/dev/null +gh pr comment "$PR_B" -R "$UPSTREAM" --body "/test-platform-e2e platform-smoke" >/dev/null +gh pr comment "$PR_OLD" -R "$UPSTREAM" --body "/test-platform-e2e platform-old" >/dev/null log "Pushing to the fork-push PR once its check exists" deadline=$((SECONDS + TIMEOUT)) @@ -148,7 +152,14 @@ log "6. Fork PR into a branch without the contract is refused before dispatch" wait_checks "$SHA_OLD" "e2e-pro: fork-old" 1 && expect neutral "$(checks "$SHA_OLD" "e2e-pro: fork-old")" "old-branch pro" wait_checks "$SHA_OLD" "e2e-oss: fork-old" 1 && expect neutral "$(checks "$SHA_OLD" "e2e-oss: fork-old")" "old-branch oss" -log "7. cache-mode: read blocked every cache save" +log "7. Platform: fork PRs run, and the same request on two PRs does not cancel" +wait_checks "$SHA_A" "e2e-platform: platform-smoke" 1 && expect success "$(checks "$SHA_A" "e2e-platform: platform-smoke")" "platform fork A" +wait_checks "$SHA_B" "e2e-platform: platform-smoke" 1 && expect success "$(checks "$SHA_B" "e2e-platform: platform-smoke")" "platform fork B" + +log "8. Platform: fork PR into a branch without the contract is refused" +wait_checks "$SHA_OLD" "e2e-platform: platform-old" 1 && expect neutral "$(checks "$SHA_OLD" "e2e-platform: platform-old")" "platform old branch" + +log "9. cache-mode: read blocked every cache save" expect 0 "$(gh api "repos/${UPSTREAM}/actions/caches?key=fork-cache-probe" --jq .total_count)" "fork-cache-probe caches" log "Done: ${FAILED} failed"