Repository navigation
docs: prefer Protocol over ProtocolBase in cross-references, per the owner's #1103 ruling #11490
Workflow file for this run
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
| name: Project Status | ||
|
Check warning on line 1 in .github/workflows/project-status.yml
|
||
| # Keeps the project board's Status field (users/JarryShaw/projects/2) in line with | ||
| # each item's state labels, which no built-in board workflow observes. The mapping | ||
| # is `status_for` in util/project_status.py; it and the PROJECT_TOKEN setup are | ||
| # documented under "Milestones and the Project Board" in | ||
| # docs/source/contributing/conventions/process.rst. | ||
| # | ||
| # SECURITY. `pull_request_target` is used only because a fork's `pull_request` run | ||
| # gets no secrets, and PROJECT_TOKEN is one. So this runs with a secret on events a | ||
| # stranger can cause, and must never execute anything the pull request supplies -- | ||
| # the classic "pwn request". The job below: | ||
| # | ||
| # * never checks out the repository, at any ref, and has no `uses:` step at all; | ||
| # * fetches util/project_status.py, and util/_actions.py, which it imports | ||
| # (#2047), through the API at `github.sha`, which under | ||
| # `pull_request_target` is the tip of the *base* branch, never the PR head -- | ||
| # so editing either in a pull request changes nothing about its own run; | ||
| # * takes nothing from the event payload but the item number, passed through | ||
| # `env:` rather than interpolated into the script, and re-reads labels and | ||
| # state from the API. | ||
| # | ||
| # tests/project/test_project_status.py asserts each of these over this file. | ||
| # | ||
| # `workflow_dispatch` is not a fork boundary: it runs the selected branch's file | ||
| # with PROJECT_TOKEN in scope. Only write access can dispatch, and write access can | ||
| # already change any workflow, so this trusts collaborators rather than forks -- | ||
| # the same trust the rest of the repository's secrets rely on. | ||
| on: | ||
| issues: | ||
| types: [opened, reopened, labeled, unlabeled, closed] | ||
| pull_request_target: | ||
| types: [opened, reopened, labeled, unlabeled, closed, ready_for_review] | ||
| schedule: | ||
| # Nightly backstop for events this workflow missed -- a run dropped from a | ||
| # full concurrency queue, a GitHub outage, or a label changed while the | ||
| # secret was absent. 00:37 UTC is clear of the Saturday jobs (#1967) and off | ||
| # the hour, which GitHub's scheduler queues most heavily. | ||
| - cron: '37 0 * * *' | ||
| workflow_dispatch: | ||
| # The job uses PROJECT_TOKEN only, never GITHUB_TOKEN. | ||
| permissions: {} | ||
| # Event runs are keyed on the item number, so the `unlabeled` and `labeled` of a | ||
| # label swap, fired within the same second, serialize on one item. Issues and pull | ||
| # requests share one number space, so keys cannot collide across the two. | ||
| # | ||
| # Nothing is cancelled: a cancelled run stays on the PR head as a failed Sync | ||
| # Status check even after a newer run has fixed the board. Omitting | ||
| # `cancel-in-progress` is not enough, because by default a group holds one pending | ||
| # run and cancels it when a newer one arrives; `queue: max` lets up to 100 wait, | ||
| # and only a run past that is cancelled. | ||
| # GitHub documents both, and that the two keys together are a validation error: | ||
| # https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/control-workflow-concurrency | ||
| # | ||
| # Queue order is not guaranteed, but runs on one item never overlap and each | ||
| # re-reads the labels, so the last to execute writes the final state. The cost is | ||
| # one run per event, not one per burst. Scheduled and manual reconciles share a | ||
| # separate group, so a reconcile can write an older Status over a fresher one; that | ||
| # self-repairs on the item's next event or the next nightly run, while one global | ||
| # group would let a reconcile hold every event run. | ||
| concurrency: | ||
| group: project-status-${{ (github.event.issue.number || github.event.pull_request.number) && format('item-{0}', github.event.issue.number || github.event.pull_request.number) || 'reconcile' }} | ||
| queue: max | ||
| jobs: | ||
| status: | ||
| name: Sync Status | ||
| runs-on: ubuntu-latest | ||
| # A single item is a handful of API calls; a full reconcile is one search | ||
| # page per 50 items plus one mutation per item that changed. | ||
| timeout-minutes: 10 | ||
| steps: | ||
| - name: Sync the project board | ||
| env: | ||
| GH_TOKEN: ${{ secrets.PROJECT_TOKEN }} | ||
| ITEM: ${{ github.event.issue.number || github.event.pull_request.number }} | ||
| SCRIPT_REF: ${{ github.sha }} | ||
| run: | | ||
| if [ -z "$GH_TOKEN" ]; then | ||
| echo '::notice title=Project Status::The PROJECT_TOKEN secret is not set, so the project board was not updated.' | ||
| exit 0 | ||
| fi | ||
| # The base branch's copy of the script, and of util/_actions.py, which | ||
| # it imports from beside it (#2047) -- see the security note above. | ||
| for script in project_status.py _actions.py; do | ||
| gh api -H 'Accept: application/vnd.github.raw' \ | ||
| "repos/${GITHUB_REPOSITORY}/contents/util/${script}?ref=${SCRIPT_REF}" \ | ||
| > "${RUNNER_TEMP}/${script}" | ||
| done | ||
| if [ -n "$ITEM" ]; then | ||
| python3 "${RUNNER_TEMP}/project_status.py" --item "$ITEM" | ||
| else | ||
| python3 "${RUNNER_TEMP}/project_status.py" --reconcile | ||
| fi | ||