-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
fix(INFRA-3631): add job-level permissions to shadow CI caller #30252
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+178
−13
Merged
Changes from 5 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
33bcfc1
fix(INFRA-3631): add job-level permissions to shadow CI caller
alucardzom 203f701
ci(INFRA-3631): skip status/comment writes on Namespace runner path
alucardzom c9f88bf
ci(INFRA-3631): dispatch Namespace shadow CI via workflow_dispatch
bsgrigorov b594287
ci(INFRA-3631): mint shadow dispatch token via GitHub App
bsgrigorov 9a2c2c4
ci(INFRA-3631): shadow dispatch via token exchange OIDC
bsgrigorov e65e564
Merge branch 'main' into phase5d/shadow-ci-job-permissions
bsgrigorov File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,26 +1,168 @@ | ||
| name: CI (Namespace shadow) | ||
|
|
||
| # Fire-and-forget dispatcher for the Namespace shadow benchmark (INFRA-3631). | ||
| # | ||
| # This workflow does NOT use `workflow_call`. Instead, it dispatches `ci.yml` | ||
| # as a separate `workflow_dispatch` run with `runner_provider=namespace`. | ||
| # | ||
| # Why a dispatch instead of a call: | ||
| # - `workflow_call` nests the called workflow's jobs into THIS workflow's | ||
| # check suite on the PR. With the repo's merge queue ALLGREEN policy, | ||
| # any shadow flake would block merges. | ||
| # - `workflow_dispatch` runs live in the Actions tab only -- invisible to | ||
| # PR checks. The shadow can fail freely; it never blocks the queue, | ||
| # never duplicates commit statuses, never posts PR comments. | ||
| # | ||
| # Correlation back to the originating PR: | ||
| # - The dispatched run's display name is set via `run-name` in ci.yml to | ||
| # `ci [shadow PR #<num> @ <sha>]`, so it's identifiable in the Actions | ||
| # tab at a glance. | ||
| # - The dispatcher job posts a GitHub Actions step summary linking the PR | ||
| # to the dispatched shadow run URL, reachable from the PR Checks tab. | ||
| # | ||
| # Authentication: Token Exchange Service (same pattern as triage-forwarder.yml | ||
| # and shared-services-workflows deploy.yml — OIDC → POST /api/exchange/token). | ||
| # | ||
| # Prerequisites: | ||
| # - Repo/org Actions variable: TOKEN_EXCHANGE_URL (already used elsewhere in | ||
| # metamask-mobile, e.g. triage-forwarder). | ||
| # - Rego policy in token-exchange-service: explicit policy | ||
| # mm-metamask-mobile-namespace-shadow-ci-token-exchange (matches OIDC | ||
| # `workflow_ref` claim for .github/workflows/ci-namespace-shadow.yml). | ||
| # | ||
| # Fork PRs: the job `if` below skips the entire job for fork head repos, so OIDC | ||
| # exchange never runs for untrusted forks (same model as not exposing secrets). | ||
|
|
||
| on: | ||
| pull_request: | ||
| types: [opened, synchronize, reopened, ready_for_review] | ||
| paths-ignore: | ||
| - 'docs/**' | ||
| - '**/*.md' | ||
| - '.github/CODEOWNERS' | ||
| - "docs/**" | ||
| - "**/*.md" | ||
| - ".github/CODEOWNERS" | ||
| push: | ||
| branches: [main] | ||
| schedule: | ||
| - cron: '0 * * * *' | ||
| - cron: "0 * * * *" | ||
| workflow_dispatch: | ||
|
|
||
| concurrency: | ||
| group: ns-shadow-${{ github.workflow }}-${{ github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| permissions: | ||
| contents: read | ||
| id-token: write | ||
|
|
||
| jobs: | ||
| shadow-ci: | ||
| name: '[shadow] CI' | ||
| uses: ./.github/workflows/ci.yml | ||
| with: | ||
| runner_provider: namespace | ||
| secrets: inherit | ||
| dispatch-shadow: | ||
| name: "[shadow] Dispatch" | ||
| runs-on: ubuntu-latest | ||
| # Fork PRs use head.repo != github.repository — skip (no shadow, no token exchange). | ||
| if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} | ||
| steps: | ||
| - name: Get OIDC token for token-exchange-service | ||
| id: oidc | ||
| run: | | ||
| set -euo pipefail | ||
| OIDC_TOKEN=$(curl -sSf -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \ | ||
| "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=api://token-exchange-service" | jq -r '.value') | ||
| echo "::add-mask::$OIDC_TOKEN" | ||
| echo "oidc_token=$OIDC_TOKEN" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Exchange for installation token (scoped permissions) | ||
| id: exchange | ||
| env: | ||
| OIDC_TOKEN: ${{ steps.oidc.outputs.oidc_token }} | ||
| TOKEN_EXCHANGE_URL: ${{ vars.TOKEN_EXCHANGE_URL }} | ||
| run: | | ||
| set -euo pipefail | ||
| if [ -z "${TOKEN_EXCHANGE_URL}" ]; then | ||
| echo "::error::TOKEN_EXCHANGE_URL Actions variable is not set. Configure it at org or repo level (see triage-forwarder.yml)." | ||
| exit 1 | ||
| fi | ||
| RESPONSE=$(curl -sSf -X POST "${TOKEN_EXCHANGE_URL}/api/exchange/token" \ | ||
| -H "Content-Type: application/json" \ | ||
| -d "$(jq -cn \ | ||
| --arg oidcToken "$OIDC_TOKEN" \ | ||
| --arg targetRepo "${{ github.repository }}" \ | ||
| '{oidcToken: $oidcToken, targetRepo: $targetRepo, requested_permissions: {metadata: "read", contents: "read", actions: "write"}}')") | ||
| STATUS=$(echo "$RESPONSE" | jq -r '.status // "ok"') | ||
| if [[ "$STATUS" == "fail" ]]; then | ||
| MSG=$(echo "$RESPONSE" | jq -r '.message // "unknown"') | ||
| echo "::error::Token exchange failed: ${MSG}" | ||
| echo "::notice::Ensure token-exchange-service policy mm-metamask-mobile-namespace-shadow-ci-token-exchange is deployed (INFRA-3631)." | ||
| exit 1 | ||
| fi | ||
| TOKEN=$(echo "$RESPONSE" | jq -r '.token // empty') | ||
| if [[ -z "$TOKEN" || "$TOKEN" == "null" ]]; then | ||
| echo "::error::Token exchange returned no token" | ||
| exit 1 | ||
| fi | ||
| echo "::add-mask::$TOKEN" | ||
| echo "token=$TOKEN" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Dispatch ci.yml on Namespace | ||
| id: dispatch | ||
| env: | ||
| GH_TOKEN: ${{ steps.exchange.outputs.token }} | ||
| REPO: ${{ github.repository }} | ||
| REF: ${{ github.head_ref || github.ref_name }} | ||
| PR_NUMBER: ${{ github.event.pull_request.number || '' }} | ||
| HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} | ||
| run: | | ||
| set -euo pipefail | ||
| echo "Dispatching ci.yml on ref='${REF}' (PR='${PR_NUMBER:-none}', sha='${HEAD_SHA}')" | ||
| gh workflow run ci.yml \ | ||
| --repo "$REPO" \ | ||
| --ref "$REF" \ | ||
| -f runner_provider=namespace \ | ||
| -f pr_number="$PR_NUMBER" \ | ||
| -f head_sha="$HEAD_SHA" | ||
|
|
||
| # gh workflow run returns immediately with no run ID. Find the run | ||
| # we just created so we can link to it from the step summary. | ||
| # Best-effort: poll briefly for a queued/in_progress dispatch on this ref. | ||
| RUN_URL="" | ||
| for _ in $(seq 1 10); do | ||
| RUN_URL=$(gh run list \ | ||
| --repo "$REPO" \ | ||
| --workflow ci.yml \ | ||
| --event workflow_dispatch \ | ||
| --branch "$REF" \ | ||
| --limit 1 \ | ||
| --json url,createdAt \ | ||
| --jq '.[0].url' 2>/dev/null || true) | ||
| if [ -n "$RUN_URL" ] && [ "$RUN_URL" != "null" ]; then | ||
| break | ||
| fi | ||
| sleep 2 | ||
| done | ||
| echo "run_url=${RUN_URL}" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Step summary | ||
| env: | ||
| PR_NUMBER: ${{ github.event.pull_request.number || '' }} | ||
| PR_URL: ${{ github.event.pull_request.html_url || '' }} | ||
| HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} | ||
| REF: ${{ github.head_ref || github.ref_name }} | ||
| RUN_URL: ${{ steps.dispatch.outputs.run_url }} | ||
| run: | | ||
| { | ||
| echo "## Namespace shadow CI dispatched" | ||
| echo | ||
| echo "| Field | Value |" | ||
| echo "|---|---|" | ||
| if [ -n "$PR_NUMBER" ]; then | ||
| echo "| PR | [#${PR_NUMBER}](${PR_URL}) |" | ||
| fi | ||
| echo "| Ref | \`${REF}\` |" | ||
| echo "| Head SHA | \`${HEAD_SHA}\` |" | ||
| if [ -n "${RUN_URL}" ]; then | ||
| echo "| Shadow run | ${RUN_URL} |" | ||
| else | ||
| echo "| Shadow run | (not yet visible — check the Actions tab) |" | ||
| fi | ||
|
bsgrigorov marked this conversation as resolved.
|
||
| echo | ||
| echo "_Shadow CI runs **fire-and-forget**: it does not appear in this PR's checks and never blocks the merge queue. Benchmark data is collected by \`scripts/namespace-benchmark.sh\`._" | ||
| } >> "$GITHUB_STEP_SUMMARY" | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.