diff --git a/.github/workflows/spec-drift.yml b/.github/workflows/spec-drift.yml index a96c793..fc375d5 100644 --- a/.github/workflows/spec-drift.yml +++ b/.github/workflows/spec-drift.yml @@ -5,6 +5,12 @@ name: API specification drift # replaces the vendored copy, with a summary of the changes and the list of # conformance tests that fail against it: the to-do list for updating the # bindings. See MAINTAINING.md. +# +# The pull request is opened with a GitHub App token, so that it triggers CI +# (pull requests opened with GITHUB_TOKEN do not) and is authored by the app's +# bot account. Configure the app with the SPEC_SYNC_APP_CLIENT_ID repository +# variable and the SPEC_SYNC_APP_PRIVATE_KEY repository secret. Without them, +# the workflow falls back to GITHUB_TOKEN. on: schedule: @@ -21,6 +27,27 @@ jobs: steps: - uses: actions/checkout@v7 + # Runs on every check, not only when the specification changed, so that + # an expired key or an uninstalled app is noticed straight away. + - uses: actions/create-github-app-token@v3 + if: vars.SPEC_SYNC_APP_CLIENT_ID != '' + id: app-token + with: + client-id: ${{ vars.SPEC_SYNC_APP_CLIENT_ID }} + private-key: ${{ secrets.SPEC_SYNC_APP_PRIVATE_KEY }} + permission-contents: write + permission-pull-requests: write + + - name: Identify the app's bot account + if: steps.app-token.outcome == 'success' + id: bot + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + APP_SLUG: ${{ steps.app-token.outputs.app-slug }} + run: | + user_id="$(gh api "/users/${APP_SLUG}[bot]" --jq .id)" + echo "identity=${APP_SLUG}[bot] <${user_id}+${APP_SLUG}[bot]@users.noreply.github.com>" >> "$GITHUB_OUTPUT" + - name: Compare with the published specification id: sync run: | @@ -76,8 +103,9 @@ jobs: - uses: peter-evans/create-pull-request@v8 if: steps.sync.outputs.changed == 'true' with: - # A token other than GITHUB_TOKEN lets the pull request trigger CI. - token: ${{ secrets.SPEC_SYNC_TOKEN || github.token }} + token: ${{ steps.app-token.outputs.token || github.token }} + author: ${{ steps.bot.outputs.identity || 'github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>' }} + committer: ${{ steps.bot.outputs.identity || 'github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>' }} branch: api-spec-update delete-branch: true commit-message: Update the vendored TypeSafe OpenAPI specification diff --git a/MAINTAINING.md b/MAINTAINING.md index 7746edf..06b74de 100644 --- a/MAINTAINING.md +++ b/MAINTAINING.md @@ -48,10 +48,38 @@ opens or updates the `api-spec-update` pull request. The description lists what was added and removed, and the conformance tests that fail against the new specification. -Pull requests opened with the default `GITHUB_TOKEN` do not trigger other -workflows. To have CI run on them, add a fine-grained personal access token -with *contents* and *pull requests* write access as the `SPEC_SYNC_TOKEN` -repository secret. +The workflow opens the pull request with a GitHub App token, so that CI runs +on it and a bot account is its author. Pull requests opened with the default +`GITHUB_TOKEN` do not trigger other workflows. Without the app, the workflow +still runs, but falls back to `GITHUB_TOKEN`, and CI has to be started by +hand (`gh workflow run ci.yml --ref api-spec-update`). + +To set up the app (once): + +1. Create a GitHub App owned by the `byteally` organisation (*Organization + settings → Developer settings → GitHub Apps → New GitHub App*): + - any name, such as `typesafe-sdk-spec-sync`, and the repository URL as + the homepage; + - under *Webhook*, clear *Active*; + - *Repository permissions*: *Contents* and *Pull requests*, both *Read and + write*; + - *Where can this GitHub App be installed?*: *Only on this account*. +2. On the app's page, note the *Client ID*, and under *Private keys* + generate a key. A `.pem` file downloads. +3. Install the app (*Install App* in the app's settings) on the `byteally` + organisation, for the `typesafe-sdk` repository only. +4. Store the client ID as a variable and the key as a secret: + + ```sh + gh variable set SPEC_SYNC_APP_CLIENT_ID --repo byteally/typesafe-sdk --body + gh secret set SPEC_SYNC_APP_PRIVATE_KEY --repo byteally/typesafe-sdk < path/to/key.pem + ``` + + Then delete the downloaded `.pem` file. +5. Check the setup with `gh workflow run spec-drift.yml --repo byteally/typesafe-sdk`. + The *create-github-app-token* and *Identify the app's bot account* steps + must succeed. They run on every check, so a revoked key or an uninstalled + app makes the daily run fail. To check by hand: