Skip to content

Commit fc192dd

Browse files
committed
ci: share one reusable head across the cron workflows (#1967)
- Conda Update, Vendor Update and GitHub Pages each carried the same test gate and `verdict` job. Both now live in cron-head.yml, a `workflow_call` workflow that each calls as its `head` job; the acting jobs read `needs.head.result` and `needs.head.outputs.verified`. - The Saturday schedules are spread eight hours apart (Pages 02:00, Vendor 10:00, Conda 18:00 UTC), so no two heads gate the same commit at once and Conda Update follows Vendor Update's release. - Triggers otherwise, concurrency groups, environments, deploy-key steps and permissions are unchanged; the head holds `contents: read` only. - Tests: the verdict and conda gate tables decide the head's jobs against cron-head.yml; new tests pin that the three call one head and the spread. - Docs: workflows.rst gains the Cron Head row, node, calls and schedule rationale, cited by name; pep.rst drops a line citation.
1 parent 390cde9 commit fc192dd

12 files changed

Lines changed: 501 additions & 312 deletions

‎.github/workflows/ci-timing.yml‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ name: CI Timing
1515
on:
1616
schedule:
1717
# Mondays 07:23 UTC: off the hour, where GitHub delays scheduled runs most,
18-
# and clear of Saturday's 02:00-10:00 crons and Project Status at 00:37.
18+
# and clear of Saturday's crons, 02:00 to 18:00 since #1967, and Project
19+
# Status at 00:37.
1920
- cron: '23 7 * * 1'
2021
workflow_dispatch:
2122
inputs:

‎.github/workflows/cron-conda.yml‎

Lines changed: 40 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,18 @@ permissions:
44
contents: read
55

66
on:
7+
# #1967: GitHub Pages, Vendor Update and Conda Update run eight hours
8+
# apart on Saturdays, so each runs `head`'s test gate alone, mostly on a
9+
# commit of its own, and `vendor-update` and `conda-update` do not queue
10+
# on `repository-maintenance` together. None reads another's output, and
11+
# Conda Update comes last so its upload finds the release Create Release
12+
# made after Vendor Update (#1634). workflows.rst gives the evidence.
713
schedule:
8-
- cron: '0 10 * * 6' # Saturdays at 10:00 UTC
14+
- cron: '0 18 * * 6' # Saturdays at 18:00 UTC
915
# No `push: branches: [main]` here. `unit-tests.yml`'s own push trigger
10-
# already runs the full matrix for this commit; calling it again below with
11-
# `gate-only: true` re-ran the single-version "Gate" job a second time for
12-
# the same tree (#715). Chaining off `Unit Tests`' completion means the gate
16+
# already runs the full matrix for this commit; calling it again on every push
17+
# with `gate-only: true` re-ran the single-version "Gate" job a second time
18+
# for the same tree (#715). Chaining off `Unit Tests`' completion means the gate
1319
# never runs twice, and `conda-update` below just reads the verdict that
1420
# already exists. `.event == 'push'` is checked in that job's `if` rather
1521
# than here, because `workflow_run` fires for a pull request's completion
@@ -25,7 +31,7 @@ on:
2531
#
2632
# A `workflow_run` is keyed on the commit `Unit Tests` verified instead (#1643).
2733
# Each `Unit Tests` completion raises a run here, and one whose verdict is for an
28-
# older commit only runs `verdict` and skips -- but in a shared group it would
34+
# older commit only runs `head` and skips -- but in a shared group it would
2935
# still displace a pending run whose verdict matches, since "any existing pending
3036
# job or workflow in the same concurrency group will be canceled" (GitHub,
3137
# "Control the concurrency of workflows and jobs").
@@ -38,53 +44,14 @@ concurrency:
3844
cancel-in-progress: false
3945

4046
jobs:
41-
# Only earns its runners on the schedule trigger: `Unit Tests` has no
42-
# schedule trigger of its own, so Saturday's cron run is the one case left
43-
# where nothing else has already verified this tree. `workflow_run` carries
44-
# its own verdict instead -- see `conda-update` below.
45-
unit-tests:
46-
name: Conda test gate
47-
if: ${{ github.event_name != 'workflow_run' }}
47+
# #1967: the test gate, on the schedule only, and the `verdict` on a
48+
# `workflow_run`, shared with Vendor Update and GitHub Pages. cron-head.yml
49+
# says when each runs.
50+
head:
51+
name: Conda gate
4852
permissions:
4953
contents: read
50-
uses: ./.github/workflows/unit-tests.yml
51-
with:
52-
gate-only: true
53-
54-
# #1643: `github.sha`, which every checkout here takes, is main's tip when the
55-
# `workflow_run` was raised -- not the commit the completed `Unit Tests` run
56-
# verified, and in a merge wave the two differ. This job compares them with
57-
# util/verdict_covers_checkout.py, whose docstring gives the rule, and the job
58-
# below acts on a `workflow_run` only when `verified` is 'true'. Its `if:` is
59-
# that job's `workflow_run` clause, so a run raised by a pull request's or a
60-
# failed `Unit Tests` run still concludes `skipped`, as before.
61-
verdict:
62-
name: Verdict covers this commit
63-
if: >-
64-
github.event_name == 'workflow_run' &&
65-
github.event.workflow_run.conclusion == 'success' &&
66-
github.event.workflow_run.event == 'push' &&
67-
github.event.workflow_run.head_branch == 'main'
68-
runs-on: ubuntu-latest
69-
timeout-minutes: 5
70-
permissions:
71-
contents: read
72-
outputs:
73-
verified: ${{ steps.verdict.outputs.verified }}
74-
steps:
75-
# The default ref, as the job below uses, so both see the same `github.sha`.
76-
# Twenty commits leaves room for the bot commits the rule accepts; a verdict
77-
# further back is not found, and so not accepted.
78-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
79-
with:
80-
fetch-depth: 20
81-
persist-credentials: false
82-
83-
- name: Compare the verified commit with this checkout
84-
id: verdict
85-
env:
86-
VERDICT_SHA: ${{ github.event.workflow_run.head_sha }}
87-
run: python3 util/verdict_covers_checkout.py
54+
uses: ./.github/workflows/cron-head.yml
8855

8956
conda-update:
9057
name: Update requirements.txt
@@ -93,27 +60,27 @@ jobs:
9360
# a job naming the environment can read it. Keep that environment free of
9461
# required reviewers, or every scheduled bump waits for an approval.
9562
environment: conda-tag
96-
needs: [ unit-tests, verdict ]
97-
# `needs` cannot itself be conditional, so a skipped gate (`workflow_run`)
98-
# has to be accepted explicitly -- by default this job would inherit the
99-
# skip. `!cancelled()` rather than `always()` so that cancelling the run
100-
# still cancels the update; a gate that actually failed still blocks.
63+
needs: [ head ]
64+
# `needs` cannot itself be conditional, and `head`'s jobs skip on every
65+
# path, so this job must not inherit a skip. `!cancelled()` rather than
66+
# `always()` so that cancelling the run still cancels the update; a gate
67+
# that actually failed still blocks.
10168
#
10269
# The `workflow_run` clause is the real gate for a push to main: it
10370
# reads the verdict `Unit Tests` already reached instead of re-running it,
104-
# and `verdict` says whether that verdict covers the commit checked out
105-
# below (#1643). `.event == 'push'` and `.head_branch == 'main'`
71+
# and `head`'s `verified` says whether that verdict covers the commit
72+
# checked out below (#1643). `.event == 'push'` and `.head_branch == 'main'`
10673
# both matter -- `workflow_run` fires for *every* completion of that
10774
# workflow, including one triggered by a pull request, and without this
10875
# check a green PR would upload a conda distribution built from that PR's
10976
# tree.
11077
if: >-
111-
!cancelled() && needs.unit-tests.result != 'failure' &&
78+
!cancelled() && needs.head.result != 'failure' &&
11279
(github.event_name != 'workflow_run' || (
11380
github.event.workflow_run.conclusion == 'success' &&
11481
github.event.workflow_run.event == 'push' &&
11582
github.event.workflow_run.head_branch == 'main' &&
116-
needs.verdict.outputs.verified == 'true'
83+
needs.head.outputs.verified == 'true'
11784
))
11885
concurrency:
11986
group: repository-maintenance
@@ -140,7 +107,7 @@ jobs:
140107
# onto main's actual tip before writing, or does not write at all, so
141108
# pinning to the exact tested sha would have bought nothing here
142109
# either. Whether this commit is one that verdict covers is
143-
# `verdict`'s question (#1643); whether what lands after it is, Push
110+
# `head`'s question (#1643); whether what lands after it is, Push
144111
# Changes' (#1632).
145112
#
146113
# The push below re-points `origin` at an SSH remote and authenticates
@@ -232,10 +199,11 @@ jobs:
232199
# Writes `pushed` and `commit`, which `CONDA_CHANGED` and `CONDA_COMMIT`
233200
# below hand to `conda-tag` and `conda-dist` (#1632).
234201
#
235-
# The verdict this run acted on -- `verdict`'s, or on the schedule the
236-
# gate's -- covers `$GITHUB_SHA`, the checkout, and nothing after it. A
237-
# commit that lands on `main` during the run would otherwise be rebased
238-
# under the bump, pushed, tagged and uploaded with no verdict of its own.
202+
# The verdict this run acted on -- `head`'s `verdict`, or on the schedule
203+
# its test gate's -- covers `$GITHUB_SHA`, the checkout, and nothing after
204+
# it. A commit that lands on `main` during the run would otherwise be
205+
# rebased under the bump, pushed, tagged and uploaded with no verdict of
206+
# its own.
239207
# So the tip is fetched first and the commits a rebase onto it would bring
240208
# in are put to #1643's rule, util/verdict_covers_checkout.py, run from
241209
# this checkout and not from the tip it is judging. If they are not
@@ -351,11 +319,14 @@ jobs:
351319
# required reviewers, or every scheduled bump waits for an approval.
352320
environment: conda-tag
353321
needs: [ conda-update ]
354-
# `!cancelled()` is what makes this job reachable at all on the
355-
# `workflow_run` path (#1632). `unit-tests` above skips on that event, and
356-
# "a default status check of `success()` is applied unless you include one
357-
# of these functions" (GitHub, "Evaluate expressions in workflows and
358-
# actions", Status check functions). The skip it reacts to is not only a
322+
# `!cancelled()` is what made this job reachable at all on the
323+
# `workflow_run` path (#1632), when the test gate was a job of this file and
324+
# skipped on that event. It is kept now that the gate is in `head` (#1967):
325+
# a job of `head` skips on every path, and this job must not hinge on how
326+
# Actions reports such a call. Without a status-check function, "a default
327+
# status check of `success()` is applied unless you include one of these
328+
# functions" (GitHub, "Evaluate expressions in workflows and actions",
329+
# Status check functions). The skip it reacts to is not only a
359330
# direct `needs:` entry's: "If a run contains a series of jobs that need
360331
# each other, a failure or skip applies to all jobs in the dependency chain
361332
# from the point of failure or skip onwards" (GitHub, "Workflow syntax for

‎.github/workflows/cron-head.yml‎

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
name: Cron Head
2+
3+
# #1967: the head that Conda Update, Vendor Update and GitHub Pages each ran as
4+
# two jobs of their own, a test gate and a `verdict`, kept in step by a test
5+
# that compared the three copies. Each now calls this file as its `head` job and
6+
# gates its own work on the call's result and its `verified` output. One task:
7+
# saying whether the commit the caller checked out may be acted on.
8+
#
9+
# Reusable only. Every expression here reads the *caller's* `github` context,
10+
# so `github.event_name` is the event that started Conda Update, Vendor Update
11+
# or GitHub Pages, and `github.sha` is the commit it checked out.
12+
on:
13+
workflow_call:
14+
outputs:
15+
verified:
16+
description: >-
17+
'true' when the `workflow_run` that raised the caller carries a
18+
Unit Tests verdict covering the commit it checked out, 'false' when
19+
it does not, and unset on any other event, where `verdict` skips.
20+
value: ${{ jobs.verdict.outputs.verified }}
21+
22+
# A called workflow cannot hold more than its caller grants, and each caller
23+
# grants this one `contents: read`. That is also what the `unit-tests.yml` call
24+
# below is granted, as Create Release's gate is.
25+
permissions:
26+
contents: read
27+
28+
jobs:
29+
# Only earns its runners when no completed Unit Tests run has verified this
30+
# tree: on the Saturday schedule, since Unit Tests has no schedule trigger of
31+
# its own. A `workflow_run` carries its own verdict, read by `verdict` below,
32+
# and a pull request never acts, so both skip the gate (#715).
33+
unit-tests:
34+
name: Test gate
35+
if: ${{ github.event_name != 'pull_request' && github.event_name != 'workflow_run' }}
36+
permissions:
37+
contents: read
38+
uses: ./.github/workflows/unit-tests.yml
39+
with:
40+
gate-only: true
41+
42+
# #1643: `github.sha`, which every checkout of the caller takes, is main's tip
43+
# when the `workflow_run` was raised -- not the commit the completed `Unit
44+
# Tests` run verified, and in a merge wave the two differ. This job compares
45+
# them with util/verdict_covers_checkout.py, whose docstring gives the rule,
46+
# and the caller acts on a `workflow_run` only when `verified` is 'true'. Its
47+
# `if:` is the caller's `workflow_run` clause, so for a run raised by a pull
48+
# request's or a failed `Unit Tests` run nothing here runs, as before.
49+
verdict:
50+
name: Verdict covers this commit
51+
if: >-
52+
github.event_name == 'workflow_run' &&
53+
github.event.workflow_run.conclusion == 'success' &&
54+
github.event.workflow_run.event == 'push' &&
55+
github.event.workflow_run.head_branch == 'main'
56+
runs-on: ubuntu-latest
57+
timeout-minutes: 5
58+
permissions:
59+
contents: read
60+
outputs:
61+
verified: ${{ steps.verdict.outputs.verified }}
62+
steps:
63+
# The default ref, as the caller's acting job uses, so both see the same
64+
# `github.sha`. Twenty commits leaves room for the bot commits the rule
65+
# accepts; a verdict further back is not found, and so not accepted.
66+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
67+
with:
68+
fetch-depth: 20
69+
persist-credentials: false
70+
71+
- name: Compare the verified commit with this checkout
72+
id: verdict
73+
env:
74+
VERDICT_SHA: ${{ github.event.workflow_run.head_sha }}
75+
run: python3 util/verdict_covers_checkout.py

0 commit comments

Comments
 (0)