Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 66 additions & 4 deletions .github/workflows/cron-conda.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,17 @@ permissions:
on:
schedule:
- cron: '0 10 * * 6' # everyday at 10am
push:
branches: [main]
# No `push: branches: [main]` here. `unit-tests.yml`'s own push trigger
# already runs the full matrix for this commit; calling it again below with
# `gate-only: true` re-ran the single-version "Gate" job a second time for
# the same tree (#715). Chaining off `Unit Tests`' completion means the gate
# never runs twice, and `conda-update` below just reads the verdict that
# already exists. `.event == 'push'` is checked in that job's `if` rather
# than here, because `workflow_run` fires for a pull request's completion
# too and a `branches:` filter here would be the only thing stopping that.
workflow_run:
workflows: ["Unit Tests"]
types: [completed]

# This workflow commits and pushes, so a cancelled run can leave the bump
# half-applied. Kept keyed on the ref, unlike the validation workflows: this
Expand All @@ -22,8 +31,13 @@ concurrency:
cancel-in-progress: false

jobs:
# Only earns its runners on the schedule trigger now: `Unit Tests` has no
# schedule trigger of its own, so Saturday's cron run is the one case left
# where nothing else has already verified this tree. `workflow_run` carries
# its own verdict instead -- see `conda-update` below.
unit-tests:
name: Conda test gate
if: ${{ github.event_name != 'workflow_run' }}
permissions:
contents: read
uses: ./.github/workflows/unit-tests.yml
Expand All @@ -34,6 +48,25 @@ jobs:
name: Update requirements.txt
runs-on: ubuntu-latest
needs: [ unit-tests ]
# `needs` cannot itself be conditional, so a skipped gate (`workflow_run`)
# has to be accepted explicitly -- by default this job would inherit the
# skip. `!cancelled()` rather than `always()` so that cancelling the run
# still cancels the update; a gate that actually failed still blocks.
#
# The `workflow_run` clause is the real gate for a push to main now: it
# reads the verdict `Unit Tests` already reached for this exact commit
# instead of re-running it. `.event == 'push'` and `.head_branch == 'main'`
# both matter -- `workflow_run` fires for *every* completion of that
# workflow, including one triggered by a pull request, and without this
# check a green PR would upload a conda distribution built from that PR's
# tree.
if: >-
!cancelled() && needs.unit-tests.result != 'failure' &&
(github.event_name != 'workflow_run' || (
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'main'
))
concurrency:
group: repository-maintenance
cancel-in-progress: false
Expand All @@ -47,6 +80,23 @@ jobs:

- uses: actions/checkout@v7
with:
# Deliberately the default `github.sha` (main's tip), not
# `github.event.workflow_run.head_sha`. Pinning to the latter once
# ran here and CodeQL flagged it as "checkout of untrusted code in a
# privileged context" -- `workflow_run` can also be raised by a pull
# request's `Unit Tests` run, and that field would then be a fork's
# commit. The `if:` above already keeps that path from reaching this
# step, but making the ref itself always resolve to a trusted
# default-branch commit removes the pattern outright rather than
# relying on CodeQL tracing through the guard. The push below rebases
# onto main's actual tip before writing regardless, so pinning to the
# exact tested sha would have bought nothing here either.
#
# The push below re-points `origin` at an SSH remote and authenticates
# with the loaded deploy key, not with this checkout's credential --
# so there is nothing here for a persisted token to do, and one less
# live credential for anything running in between to find.
persist-credentials: false
fetch-depth: 0

- uses: actions/setup-python@v7
Expand Down Expand Up @@ -131,20 +181,32 @@ jobs:
token: ${{ secrets.PYPCAPKIT }}
fetch-depth: 0

# `env:` rather than splicing `needs.*.outputs` straight into the script --
# CodeQL flags the latter as code injection, since Actions substitutes
# `${{ }}` into the script text before the shell ever parses it, so a
# value containing shell metacharacters runs as script rather than data.
# Routing it through `env:` and reading `$PCAPKIT_VERSION`/`$PCAPKIT_BUILD`
# keeps it a shell variable, which the shell can only ever treat as data.
- name: Create Tag
env:
PCAPKIT_VERSION: ${{ needs.conda-update.outputs.PCAPKIT_VERSION }}
PCAPKIT_BUILD: ${{ needs.conda-update.outputs.PCAPKIT_BUILD }}
run: |
set -x

git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"

git pull
git tag "conda-${{ needs.conda-update.outputs.PCAPKIT_VERSION }}+${{ needs.conda-update.outputs.PCAPKIT_BUILD }}" -m"Conda Build"
git tag "conda-${PCAPKIT_VERSION}+${PCAPKIT_BUILD}" -m"Conda Build"

- name: Push Tag
env:
PCAPKIT_VERSION: ${{ needs.conda-update.outputs.PCAPKIT_VERSION }}
PCAPKIT_BUILD: ${{ needs.conda-update.outputs.PCAPKIT_BUILD }}
run: |
git remote set-url origin git@github.com:JarryShaw/PyPCAPKit.git
git push origin "conda-${{ needs.conda-update.outputs.PCAPKIT_VERSION }}+${{ needs.conda-update.outputs.PCAPKIT_BUILD }}"
git push origin "conda-${PCAPKIT_VERSION}+${PCAPKIT_BUILD}"

conda-dist:
name: Conda deployment (update) on ${{ matrix.os }} with Python ${{ matrix.python-version }}
Expand Down
61 changes: 59 additions & 2 deletions .github/workflows/cron-vendor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,25 @@ name: "Vendor Update"
on:
schedule:
- cron: '0 10 * * 6' # everyday at 10am
push:
branches: [main]
# No `push: branches: [main]` here. `unit-tests.yml`'s own push trigger
# already runs the full matrix for this commit; calling it again below with
# `gate-only: true` re-ran the single-version "Gate" job a second time for
# the same tree (#715). Chaining off `Unit Tests`' completion means the gate
# never runs twice, and `vendor-update` below just reads the verdict that
# already exists. `.event == 'push'` is checked in that job's `if` rather
# than here, because `workflow_run` fires for a pull request's completion
# too and a `branches:` filter here would be the only thing stopping that.
workflow_run:
workflows: ["Unit Tests"]
types: [completed]

# Least privilege by default now that `workflow_run` is in the trigger list --
# that event can be raised by a pull request's `Unit Tests` run completing, so
# the ambient token this workflow gets should default to nothing rather than
# whatever the repository's default is. Both jobs below already declare their
# own narrower `permissions:`, so this changes nothing they rely on; it only
# closes the gap for anything added later that forgets to.
permissions: {}

# This workflow commits and pushes, so a cancelled run can leave the bump
# half-applied. Kept keyed on the ref, unlike the validation workflows: a dropped
Expand All @@ -20,8 +37,13 @@ concurrency:
cancel-in-progress: false

jobs:
# Only earns its runners on the schedule trigger now: `Unit Tests` has no
# schedule trigger of its own, so Saturday's cron run is the one case left
# where nothing else has already verified this tree. `workflow_run` carries
# its own verdict instead -- see `vendor-update` below.
unit-tests:
name: Vendor test gate
if: ${{ github.event_name != 'workflow_run' }}
permissions:
contents: read
uses: ./.github/workflows/unit-tests.yml
Expand All @@ -31,6 +53,24 @@ jobs:
vendor-update:
runs-on: macos-latest
needs: [ unit-tests ]
# `needs` cannot itself be conditional, so a skipped gate (`workflow_run`)
# has to be accepted explicitly -- by default this job would inherit the
# skip. `!cancelled()` rather than `always()` so that cancelling the run
# still cancels the update; a gate that actually failed still blocks.
#
# The `workflow_run` clause is the real gate for a push to main now: it
# reads the verdict `Unit Tests` already reached for this exact commit
# instead of re-running it. `.event == 'push'` and `.head_branch == 'main'`
# both matter -- `workflow_run` fires for *every* completion of that
# workflow, including one triggered by a pull request, and without this
# check a green PR would push a vendor-registry commit from that PR's tree.
if: >-
!cancelled() && needs.unit-tests.result != 'failure' &&
(github.event_name != 'workflow_run' || (
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'main'
))
concurrency:
group: repository-maintenance
cancel-in-progress: false
Expand All @@ -43,6 +83,23 @@ jobs:
ssh-private-key: ${{ secrets.PYPCAPKIT_ACTIONS_DEPLOY_KEY }}

- uses: actions/checkout@v7
with:
# Deliberately the default `github.sha` (main's tip), not
# `github.event.workflow_run.head_sha`. Pinning to the latter once
# ran here and CodeQL flagged it as "checkout of untrusted code in a
# privileged context" -- `workflow_run` can also be raised by a pull
# request's `Unit Tests` run, and that field would then be a fork's
# commit. The `if:` above already keeps that path from reaching this
# step, but making the ref itself always resolve to a trusted
# default-branch commit removes the pattern outright rather than
# relying on CodeQL tracing through the guard. The push below rebases
# onto main's actual tip before writing regardless, so pinning to the
# exact tested sha would have bought nothing here either.
# The push below re-points `origin` at an SSH remote and authenticates
# with the loaded deploy key, not with this checkout's credential --
# so there is nothing here for a persisted token to do, and one less
# live credential for anything running in between to find.
persist-credentials: false

- uses: actions/setup-python@v7
with:
Expand Down
59 changes: 47 additions & 12 deletions .github/workflows/deploy-pages.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
name: GitHub Pages

on:
push:
branches: [main]
# No `push: branches: [main]` here. `unit-tests.yml`'s own push trigger already
# runs the full matrix for this commit; calling it again with `gate-only: true`
# below re-ran the single-version "Gate" job a second time for the same tree
# (#715). Chaining off `Unit Tests`' completion instead means the gate never
# runs twice, and this workflow simply waits for the verdict that already
# exists. `workflow_run` fires for *any* completion of that workflow, PR or
# push, so the jobs below filter on `.event == 'push'` themselves rather than
# trusting a `branches:` filter here to do it -- see the note on `deploy-pages`.
workflow_run:
workflows: ["Unit Tests"]
types: [completed]
schedule:
- cron: '0 2 * * 6'
pull_request:
Expand All @@ -25,13 +34,14 @@ concurrency:
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
# Nothing is published on a pull request -- the Deploy step below is skipped
# -- so the gate only earns its runners when a real publish follows. The docs
# build still runs on pull requests, which is the part that actually
# validates a documentation change.
# Only earns its runners on the schedule trigger now: `Unit Tests` has no
# schedule trigger of its own, so Saturday's cron run is the one case left
# where nothing else has already verified this tree, and a gate is still
# needed. Pull requests never published anyway, and `workflow_run` carries
# its own verdict from the run that completed -- see `deploy-pages` below.
unit-tests:
name: Docs test gate
if: ${{ github.event_name != 'pull_request' }}
if: ${{ github.event_name != 'pull_request' && github.event_name != 'workflow_run' }}
permissions:
contents: read
uses: ./.github/workflows/unit-tests.yml
Expand All @@ -40,17 +50,42 @@ jobs:

deploy-pages:
needs: [ unit-tests ]
# `needs` cannot itself be conditional, so a skipped gate (pull requests)
# has to be accepted explicitly -- by default this job would inherit the
# skip. `!cancelled()` rather than `always()` so that cancelling the run
# still cancels the docs build; a gate that actually failed still blocks.
if: ${{ !cancelled() && needs.unit-tests.result != 'failure' }}
# `needs` cannot itself be conditional, so a skipped gate (pull requests,
# and now `workflow_run`) has to be accepted explicitly -- by default this
# job would inherit the skip. `!cancelled()` rather than `always()` so that
# cancelling the run still cancels the docs build; a gate that actually
# failed still blocks.
#
# The `workflow_run` clause is the real gate for a push to main now: it
# reads the verdict `Unit Tests` already reached for this exact commit
# instead of re-running it. `.event == 'push'` and `.head_branch == 'main'`
# both matter -- `workflow_run` fires for *every* completion of that
# workflow, including one triggered by a pull request, and without this
# check a green PR would publish docs from that PR's tree.
if: >-
!cancelled() && needs.unit-tests.result != 'failure' &&
(github.event_name != 'workflow_run' || (
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'main'
))
concurrency: ci-${{ github.ref }} # Recommended if you intend to make multiple deployments in quick succession.
runs-on: macos-latest
steps:
- name: Checkout 🛎️
uses: actions/checkout@v7
with:
# Deliberately the default `github.sha` (main's tip), not
# `github.event.workflow_run.head_sha`. Pinning to the latter once
# ran here and CodeQL flagged it as "checkout of untrusted code in a
# privileged context" -- `workflow_run` can also be raised by a pull
# request's `Unit Tests` run, and that field would then be a fork's
# commit. The `if:` above already keeps that path from reaching this
# step, but making the ref itself always resolve to a trusted
# default-branch commit removes the pattern outright rather than
# relying on CodeQL tracing through the guard. `main`'s tip is also
# the *right* commit for docs, which should reflect current main,
# not a slightly older one -- so this is not a compromise here.
persist-credentials: false

- uses: actions/setup-python@v7
Expand Down
Loading