|
| 1 | +# Ask the private meshStack mono repo to run this repository's acceptance suite and report the |
| 2 | +# result back as a check run. The suite needs a whole meshStack backend, so it cannot run here; |
| 3 | +# this workflow only asks for it. |
| 4 | +name: Acceptance Tests |
| 5 | + |
| 6 | +# The push trigger is not decoration: running on the main push, and not only on pull requests, is |
| 7 | +# what surfaces a CLI/backend regression before a release tag. |
| 8 | +on: |
| 9 | + pull_request_target: |
| 10 | + push: |
| 11 | + branches: |
| 12 | + - main |
| 13 | + # TEMPORARY, delete before merge. A pull_request_target run takes this file from the base |
| 14 | + # branch, where it does not exist yet, so the dispatcher cannot try itself out on its own pull |
| 15 | + # request. A push event takes the pushed branch's own file, and is the only trigger that can. |
| 16 | + - feature/cli-satellite |
| 17 | + |
| 18 | +permissions: |
| 19 | + contents: read |
| 20 | + |
| 21 | +jobs: |
| 22 | + # Reads "Acceptance Tests / request" in the checks list. No `name:`, unlike test.yml's jobs: a |
| 23 | + # name pins a stable string for a check that gates a merge, and nothing gates on this one. The |
| 24 | + # gating check is "Acceptance Tests (meshStack backend)", which meshfed-release posts. |
| 25 | + request: |
| 26 | + runs-on: ubuntu-latest |
| 27 | + env: |
| 28 | + SATELLITE_REF: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.ref || github.ref_name }} |
| 29 | + |
| 30 | + # Two things have to hold, and naming them once here keeps the steps below to one condition each. |
| 31 | + # The run has to be in our own repository, because a fork of it holds neither the app secrets nor |
| 32 | + # a branch anyone gates on. And on a pull request the head branch has to live in this repository, |
| 33 | + # which means its author has write access here: the code under test is then code we already trust. |
| 34 | + DISPATCH: ${{ github.repository_owner == 'meshcloud' && (github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository) }} |
| 35 | + |
| 36 | + # This workflow MUST NOT check out the pull request, and has no `actions/checkout` for that |
| 37 | + # reason. `pull_request_target` runs in the base repo's context with its secrets, so checking out |
| 38 | + # contributor code here would be the classic "pwn request" hole. Reading `github.event` and |
| 39 | + # calling an API is passive use of that context and safe. |
| 40 | + steps: |
| 41 | + # Without this the contributor sees a required check that never reports and no reason for it. |
| 42 | + - name: Explain a skipped fork pull request |
| 43 | + if: env.DISPATCH != 'true' && github.event_name == 'pull_request_target' |
| 44 | + env: |
| 45 | + BASE_REPO: ${{ github.repository }} |
| 46 | + run: echo "::notice::Acceptance tests are not dispatched for a fork pull request. A maintainer has to adopt the branch into $BASE_REPO before the suite can run against it." |
| 47 | + |
| 48 | + # Downscoped to `actions: write` at mint time even though the installation carries nothing |
| 49 | + # else, so a later widening of the app cannot leak into this workflow. |
| 50 | + - name: Mint a token for the dispatch |
| 51 | + id: token |
| 52 | + if: env.DISPATCH == 'true' |
| 53 | + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 |
| 54 | + with: |
| 55 | + # The secret holds the numeric app id, not a client id, and that is fine: the action reads |
| 56 | + # `client-id` or the deprecated `app-id` into one value, and GitHub takes either as the JWT |
| 57 | + # issuer. The name stays because it is an organization secret every satellite reads. |
| 58 | + client-id: ${{ secrets.SATELLITE_GH_APP_ID }} |
| 59 | + private-key: ${{ secrets.SATELLITE_GH_APP_PRIVATE_KEY }} |
| 60 | + owner: meshcloud |
| 61 | + repositories: meshfed-release |
| 62 | + permission-actions: write |
| 63 | + |
| 64 | + # meshfed-release pairs a satellite branch with a same-named branch of its own, and the dispatch |
| 65 | + # names that branch rather than always `develop`. A `workflow_dispatch` reads both the workflow |
| 66 | + # file and the checkout it makes from the ref it is given, so dispatching to `develop` would run |
| 67 | + # the orchestration that is already merged and never the change to it that a paired branch |
| 68 | + # carries. This token may dispatch workflows in meshfed-release and read nothing there, so a |
| 69 | + # rejected dispatch is the only branch lookup available here. It also answers the better |
| 70 | + # question: not whether the branch exists, but whether it carries a workflow this can dispatch. |
| 71 | + # |
| 72 | + # `$SATELLITE_REF` reaches the script through the environment, and never as a `${{ }}` expression |
| 73 | + # that GitHub would substitute into the script text before bash reads it. A branch named `$(id)` |
| 74 | + # would otherwise run as a command. |
| 75 | + - name: Request the acceptance run |
| 76 | + if: env.DISPATCH == 'true' |
| 77 | + env: |
| 78 | + GH_TOKEN: ${{ steps.token.outputs.token }} |
| 79 | + run: | |
| 80 | + set -euo pipefail |
| 81 | + request() { |
| 82 | + gh workflow run ci-satellite.yml \ |
| 83 | + --repo meshcloud/meshfed-release \ |
| 84 | + --ref "$1" \ |
| 85 | + -f repo=meshstack-cli \ |
| 86 | + -f branch_name="$SATELLITE_REF" |
| 87 | + } |
| 88 | + if request "$SATELLITE_REF"; then |
| 89 | + orchestrated_from="$SATELLITE_REF" |
| 90 | + else |
| 91 | + echo "::notice::meshfed-release has no branch $SATELLITE_REF to dispatch, so develop orchestrates this run." |
| 92 | + request develop |
| 93 | + orchestrated_from=develop |
| 94 | + fi |
| 95 | + echo "::notice::Requested an acceptance run for $SATELLITE_REF, orchestrated from meshfed-release $orchestrated_from. The result arrives as the \"Acceptance Tests (meshStack backend)\" check." |
0 commit comments