You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 3ea7531
Browse filesBrowse the repository at this point in the historyBrowse files
fix(ci): fork e2e — Chainsaw+TIA run and image-push hardening
The fork e2e path in e2e-fork.yaml still ran the pre-#2826 BATS app loop
(`hack/e2e-apps/*.bats` + `test-apps-%`), which the merged Chainsaw
migration deleted — so the fork run was already broken. Rewrite it to the
same Chainsaw + Test-Impact-Analysis path as the in-tree job, and clear the
remaining review follow-ups on the publish job.
- Run E2E via a single `make test-chainsaw` invocation (no per-app retry —
e2e-testing.md §1). Suite selection and the full-e2e override come from
`resolve`'s TRUSTED base-repo listFiles/labels (new `changed_b64` /
`full_e2e` outputs), never a git diff of the fork-controlled merge ref, so
TIA cannot be shaped into skipping the suite (keeps the #3257 property).
Collect + upload the Chainsaw JUnit report like the in-tree job.
- Restrict fork image pushes to a trusted allowlist derived from the base
parent (HEAD^1) Makefiles, so a fork cannot mint an arbitrary repo path
under the CI registry by renaming an image. The charset guard stays as
defence in depth.
- `skopeo copy --all` so a multi-platform build's index digest (the one
baked into pr.patch) is preserved.
- Pin the flux CLI (FLUX_VERSION) in the privileged publish job instead of
install.sh's rolling latest.
Docs: e2e-testing.md §10 records that the fork path now runs Chainsaw + TIA
and that fork suite selection must stay sourced from trusted data.
Assisted-By: Claude <[email protected]>
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Signed-off-by: Myasnikov Daniil <[email protected]>
echo "Changed files:"; sed 's/^/ /' /tmp/changed.txt
475
+
apps="$(./hack/select-e2e.sh /tmp/changed.txt)"
476
+
echo "Selected apps: ${apps:-<none>}"
477
+
echo "apps=${apps}" >> "$GITHUB_OUTPUT"
478
+
if [ -z "${apps}" ]; then
479
+
echo "skip=true" >> "$GITHUB_OUTPUT"
480
+
else
481
+
echo "skip=false" >> "$GITHUB_OUTPUT"
482
+
fi
483
+
# Single `chainsaw test` invocation, no per-app retry loop (e2e-testing.md
484
+
# §1) — Chainsaw polls each assertion to its own timeout. Full suite when
485
+
# the full-e2e label skipped selection or the selection is empty.
486
+
- name: Run E2E tests
487
+
id: e2e_tests
488
+
if: ${{ steps.select.outputs.skip != 'true' }}
489
+
env:
490
+
SELECTED_APPS: ${{ steps.select.outputs.apps }}
491
+
FULL_E2E: ${{ needs.resolve.outputs.full_e2e }}
492
+
run: |
493
+
cd "/tmp/$SANDBOX_NAME"
494
+
if [ "$FULL_E2E" = "true" ] || [ -z "$SELECTED_APPS" ]; then
495
+
suites="" # empty => test-chainsaw runs the whole suite
496
+
echo "Running full chainsaw suite"
497
+
else
498
+
suites="$SELECTED_APPS"
499
+
echo "Running selected chainsaw suites: $suites"
500
+
fi
501
+
if ! make -C packages/core/testing SANDBOX_NAME=$SANDBOX_NAME test-chainsaw CHAINSAW_SUITES="$suites"; then
502
+
echo "❌ Chainsaw E2E failed (see the assertion diffs above and diagnostics below)"
503
+
echo "::group::Diagnostics: HelmRelease status"
504
+
docker exec "$SANDBOX_NAME" sh -c 'kubectl get hr -A -o wide 2>&1 | tail -50' || true
505
+
echo "::endgroup::"
506
+
echo "::group::Diagnostics: recent events"
507
+
docker exec "$SANDBOX_NAME" sh -c 'kubectl get events -A --sort-by=.lastTimestamp 2>&1 | tail -30' || true
508
+
echo "::endgroup::"
509
+
echo "::group::Diagnostics: COSI bucket state"
510
+
docker exec "$SANDBOX_NAME" sh -c '
511
+
kubectl get bucketclaims.objectstorage.k8s.io -A -o custom-columns="NAMESPACE:.metadata.namespace,NAME:.metadata.name,READY:.status.bucketReady,BUCKET:.status.bucketName" 2>&1
512
+
kubectl get buckets.objectstorage.k8s.io -o custom-columns="NAME:.metadata.name,READY:.status.bucketReady,ID:.status.bucketID,CLAIMNS:.spec.bucketClaim.namespace" 2>&1
513
+
kubectl get bucketaccesses.objectstorage.k8s.io -A -o custom-columns="NAMESPACE:.metadata.namespace,NAME:.metadata.name,GRANTED:.status.accessGranted,CLAIM:.spec.bucketClaimName" 2>&1
Copy file name to clipboardExpand all lines: docs/agents/e2e-testing.md
+4-2Lines changed: 4 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -102,14 +102,16 @@ Be precise about TIA's scope — it is narrower than "skip E2E for unrelated PRs
102
102
A fork's `pull_request` run carries no registry credentials, so it cannot push the images the e2e sandbox pulls. E2E therefore runs across **two** workflows:
103
103
104
104
- **`.github/workflows/pull-requests.yaml`** runs e2e in-tree for **same-repo** PRs (the `e2e` job, named `E2E (in-tree)`). Fork PRs skip this job.
105
-
- **`.github/workflows/e2e-fork.yaml`** runs e2e for **fork** PRs via a privileged `workflow_run` that fires after the fork's Pull Request run completes. It pushes the fork-built images to the registry and runs the same suite in an ephemeral sandbox that itself has no push credentials.
105
+
- **`.github/workflows/e2e-fork.yaml`** runs e2e for **fork** PRs via a privileged `workflow_run` that fires after the fork's Pull Request run completes. It pushes the fork-built images to the registry and runs the suite — the same Chainsaw + TIA path as in-tree (§7) — in an ephemeral sandbox that itself has no push credentials.
106
106
107
-
Two consequences to keep in mind when touching either workflow:
107
+
Consequences to keep in mind when touching either workflow:
108
108
109
109
- **Fork builds export, they do not push (`OCI_EXPORT_DIR`).** The fork's unprivileged build sets `OCI_EXPORT_DIR` (see `hack/common-envs.mk`) so every image is written to a per-image OCI archive artifact instead of being pushed; the privileged `e2e-fork.yaml` later pushes those archives to the registry **by digest**. Any build recipe that shells out to push directly — e.g. a `skopeo copy … docker://…` that bypasses the `image-tags`/`oci-output` macros — must honour `OCI_EXPORT_DIR` too, or a fork build dies with an anonymous-push `denied` (#3257). This is covered by `hack/common-envs_test.bats`.
110
110
- **"E2E Tests" is a synthetic check-run, not a job.** The required merge-gate context named `E2E Tests` is an explicit GitHub check-run — concluded by the `e2e-report` job for same-repo PRs and by `e2e-fork.yaml` for forks — **not** the `e2e` job itself. This is deliberate: a required *job* that is skipped (as the `e2e` job is for forks) counts as passed by branch protection, which is exactly how fork PRs used to merge with e2e never run (#3257). If you rename the `e2e` job, do **not** name it `E2E Tests` and do not wire branch protection to a job name — the check-run must stay the gate.
111
111
- **The check-run is published by the COZYSTACK_CI GitHub App, not the default `GITHUB_TOKEN`.** A token-created check-run is owned by the app that created it, and GitHub files it under that app's check-suite. Created with the default token (the "GitHub Actions" app) a *floating* check-run — one with no owning job — attaches to an arbitrary GitHub Actions check-suite for the head SHA, in practice the earliest-registered one, which is a tiny labeler workflow; that is why `E2E Tests` used to render under "PR size label". Publishing it as the app (both `e2e-report` in `pull-requests.yaml` and the `open`/`report` steps in `e2e-fork.yaml` pass `github-token: ${{ steps.app-token.outputs.token }}`) gives the check its own dedicated suite shown under the app's name, and is stricter — only the CI app can satisfy the gate, not any workflow holding the default token. The app token is minted with `permission-checks: write` only; in `e2e-fork.yaml`'s `resolve` the PR/artifact lookups stay on the default `workflow_run` token, so the decision step and the check-publishing step use different tokens by design. **Consequence for branch protection:** the required `E2E Tests` context must be required *from the COZYSTACK_CI app's id*, not from GitHub Actions (`app_id 15368`). If it is pinned to GitHub Actions, the app-published check will not satisfy the gate and every PR stays "Expected"; conversely a `checks.create`/`checks.update` must always run as the same app that opened the pending run (the fork `report` step re-mints the token for exactly this reason).
112
112
113
+
- **Fork TIA is driven from trusted data, not a merge-ref diff.** `e2e-fork.yaml`'s `e2e` job runs the same `select-e2e.sh` TIA as in-tree (§7), but a `workflow_run` job cannot trust a `git diff` of the fork-controlled merge ref — a fork could shape that diff so TIA selects nothing and the suite is skipped. So the changed-file list and the `full-e2e` label both come from `resolve`'s base-repo API lookups (`pulls.listFiles` / the PR's labels), the same trusted source as the docs-only decision, passed to the `e2e` job as outputs (`changed_b64`, `full_e2e`). Keep it that way: never re-derive the fork selection from the checked-out tree.
114
+
113
115
**Security prerequisite (repo setting).** The split is only safe if fork runs require maintainer approval before they execute. The repository must keep **Settings → Actions → "Require approval for all outside collaborators"** enabled, so a fork's `pull_request` run — which produces the artifacts `e2e-fork.yaml` consumes — cannot run untrusted code (or feed the privileged run) without a maintainer's go-ahead.
0 commit comments