mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 02:54:26 +00:00
The workflows pin `runs-on: ubuntu-NN.NN`, so no job moves to a new Ubuntu release behind our back, but nothing raised the pin either. A sixth job of `🔄 Update versions` closes that: `utils/update/runner.py` reads the image table of actions/runner-images, takes the newest x64 Ubuntu label whose image carries no preview or beta badge, and raises every older bare `runs-on: ubuntu-NN.NN` to it; arch-suffixed labels stay untouched. The job opens the change as one PR over `.github/workflows`, so its CI run tests the new image before it reaches main. This commit carries the updater's first result: every job now runs on ubuntu-26.04 (49 `runs-on`). actionlint 1.7.12, the newest release, does not know `ubuntu-26.04` yet (rhysd/actionlint#682, fix proposed in rhysd/actionlint#743), so `.github/actionlint.yaml` declares the label until a release knows it. act maps each `ubuntu-NN.NN` label the workflows use to the local platform image, derived from `.github/workflows`, instead of a fixed list that did not know ubuntu-26.04. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
386 lines
14 KiB
YAML
386 lines
14 KiB
YAML
name: "🎵 CI: Orchestrator"
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
distros:
|
|
description: "Distro pool the deploy matrix spreads its rows over. Empty: every declared distro, which is what a sweep wants. The rows rotate over the pool, so narrowing it only makes sense to chase one distribution."
|
|
required: false
|
|
type: string
|
|
default: ""
|
|
whitelist:
|
|
description: "Optional space-separated whitelist of app ids"
|
|
required: false
|
|
type: string
|
|
default: ""
|
|
lifecycles:
|
|
description: "Lifecycle envelope for test-deploy discovery (comma/space-separated)."
|
|
required: false
|
|
type: string
|
|
default: ""
|
|
mode:
|
|
description: "Deploy modes rows may be drawn from: 'auto' rotates every row over the modes its role offers; a subset of 'host compose swarm' keeps only the rows those modes can take."
|
|
required: false
|
|
type: string
|
|
default: "auto"
|
|
priority:
|
|
description: "Optional space-separated app ids for the priority line: sorted to the head of the list and cut into chunks of their own, so every priority row is deployed before the first regular one starts. Priority rows skip the sweep rotation and run every combination they can take (each variant, each offered mode, Tor and clearnet)."
|
|
required: false
|
|
type: string
|
|
default: ""
|
|
sweep:
|
|
description: "Sweep number driving the mode and tor rotation. Empty: the run number."
|
|
required: false
|
|
type: string
|
|
default: ""
|
|
offset:
|
|
description: "Regular rows to skip before filling the chunks. 0 starts at the head of the ranking; raise it to reach rows the budget leaves behind."
|
|
required: false
|
|
type: string
|
|
default: "0"
|
|
chunk_size:
|
|
description: "📦 Rows one chunk holds. Empty or 0 derives it from the runner slots a chunk may assume and the waves the queue window affords. Typed string for the same reason as resume_from_chunk."
|
|
required: false
|
|
type: string
|
|
default: "0"
|
|
resume_from_chunk:
|
|
description: "⏭️ Skip every chunk below this index. Use to continue a sweep after fixing what broke it, instead of re-running the green chunks. Typed string, not number: a workflow_dispatch number input reaches the caller as a string, and handing that to a number-typed workflow_call input makes GitHub reject the job before it is created."
|
|
required: false
|
|
type: string
|
|
default: "0"
|
|
chunk_gate:
|
|
description: "🛑 Stop the chunk chain at its first failed chunk. Off: keep deploying the remaining chunks."
|
|
required: false
|
|
type: boolean
|
|
default: true
|
|
filesystem:
|
|
description: "🗄️ Filesystem pool the deploy matrix spreads its rows over (ext4, btrfs, zfs). Empty: all three."
|
|
required: false
|
|
type: string
|
|
default: ""
|
|
image_wait_attempts:
|
|
description: "Polling attempts for waiting on prebuilt or mirrored images"
|
|
required: false
|
|
type: string
|
|
default: "1980"
|
|
image_wait_sleep_seconds:
|
|
description: "Polling interval in seconds for waiting on prebuilt or mirrored images"
|
|
required: false
|
|
type: string
|
|
default: "10"
|
|
workspace:
|
|
description: "Run the workspace test (dev-runtime image matrix). 'auto' runs it on a global scope only, 'true'/'false' force it."
|
|
required: false
|
|
type: string
|
|
default: "auto"
|
|
workspace_track:
|
|
description: "Which workspace track each distro runs: 'rotate' splits them across compose and swarm and flips with the run number, 'compose'/'swarm' pin every distro."
|
|
required: false
|
|
type: string
|
|
default: "rotate"
|
|
tor:
|
|
description: "Tor axis of the deploy matrix: 'auto' rotates the onion over the tor-capable rows, 'enforced' onions all of them, 'exclusive' also drops the rows that cannot, 'disabled' onions none."
|
|
required: false
|
|
type: string
|
|
default: "auto"
|
|
vpn:
|
|
description: "WireGuard axis of the deploy matrix: 'auto' rotates the mesh over the swarm rows, 'enforced' meshes all of them, 'disabled' meshes none."
|
|
required: false
|
|
type: string
|
|
default: "auto"
|
|
meshes:
|
|
description: "WireGuard layout a meshed swarm row deploys, e.g. an inventories/examples/*/wireguard.yml. Empty: INFINITO_SWARM_MESHES from default.env."
|
|
required: false
|
|
type: string
|
|
default: ""
|
|
trusted_pr:
|
|
description: "True when the PR carries the maintainer '🛡️ Trusted' label; skips the fork-prereq wait."
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
|
|
permissions:
|
|
actions: read
|
|
contents: read
|
|
packages: read
|
|
|
|
jobs:
|
|
validate-selection:
|
|
name: 🎯 Selection
|
|
uses: ./.github/workflows/call-validation.yml
|
|
with:
|
|
whitelist: ${{ inputs.whitelist }}
|
|
priority: ${{ inputs.priority }}
|
|
mode: ${{ inputs.mode }}
|
|
tor: ${{ inputs.tor }}
|
|
distros: ${{ inputs.distros }}
|
|
filesystem: ${{ inputs.filesystem }}
|
|
lifecycles: ${{ inputs.lifecycles }}
|
|
fork_prereqs: true
|
|
trusted_pr: ${{ inputs.trusted_pr }}
|
|
image_wait_attempts: ${{ inputs.image_wait_attempts }}
|
|
image_wait_sleep_seconds: ${{ inputs.image_wait_sleep_seconds }}
|
|
secrets: inherit
|
|
|
|
security-codeql:
|
|
needs: validate-selection
|
|
permissions:
|
|
contents: read
|
|
packages: read
|
|
security-events: write
|
|
uses: ./.github/workflows/cron-security-codeql.yml
|
|
|
|
lint:
|
|
needs: validate-selection
|
|
uses: ./.github/workflows/call-lint.yml
|
|
|
|
test:
|
|
needs: validate-selection
|
|
uses: ./.github/workflows/call-test.yml
|
|
|
|
build-ci-images:
|
|
needs: validate-selection
|
|
permissions:
|
|
contents: read
|
|
packages: write
|
|
uses: ./.github/workflows/call-images-build-ci.yml
|
|
with:
|
|
distros: "arch debian ubuntu fedora centos"
|
|
concurrency_channel: ci-orchestrator-build-consumer
|
|
secrets: inherit
|
|
|
|
test-dns:
|
|
needs:
|
|
- build-ci-images
|
|
- images-mirror-missing
|
|
- validate-selection
|
|
uses: ./.github/workflows/call-test-dns.yml
|
|
with:
|
|
distros: "arch debian ubuntu fedora centos"
|
|
|
|
images-mirror-missing:
|
|
needs: validate-selection
|
|
permissions:
|
|
contents: read
|
|
packages: write
|
|
uses: ./.github/workflows/call-images-mirror-missing.yml
|
|
with:
|
|
concurrency_channel: ci-orchestrator-mirror-consumer
|
|
image_wait_attempts: ${{ inputs.image_wait_attempts }}
|
|
image_wait_sleep_seconds: ${{ inputs.image_wait_sleep_seconds }}
|
|
secrets: inherit
|
|
|
|
test-deploy-chunk-0:
|
|
name: 🧱 Chunk 0
|
|
if: >-
|
|
${{ !cancelled()
|
|
&& inputs.resume_from_chunk <= 0
|
|
&& needs.lint.result == 'success'
|
|
&& needs.test.result == 'success'
|
|
&& needs.build-ci-images.result == 'success'
|
|
&& needs.images-mirror-missing.result == 'success' }}
|
|
needs:
|
|
- lint
|
|
- test
|
|
- build-ci-images
|
|
- images-mirror-missing
|
|
- validate-selection
|
|
uses: ./.github/workflows/call-test-deploy.yml
|
|
with:
|
|
distros: ${{ inputs.distros }}
|
|
index: "0"
|
|
sweep: ${{ inputs.sweep != '' && inputs.sweep || github.run_number }}
|
|
offset: ${{ inputs.offset }}
|
|
chunk_size: ${{ inputs.chunk_size }}
|
|
whitelist: ${{ inputs.whitelist }}
|
|
priority: ${{ inputs.priority }}
|
|
lifecycles: ${{ inputs.lifecycles }}
|
|
modes: ${{ inputs.mode }}
|
|
filesystem: ${{ inputs.filesystem }}
|
|
tor: ${{ inputs.tor }}
|
|
vpn: ${{ inputs.vpn }}
|
|
meshes: ${{ inputs.meshes }}
|
|
|
|
test-deploy-chunk-1:
|
|
name: 🧱 Chunk 1
|
|
if: >-
|
|
${{ !cancelled()
|
|
&& inputs.resume_from_chunk <= 1
|
|
&& needs.lint.result == 'success'
|
|
&& needs.test.result == 'success'
|
|
&& needs.build-ci-images.result == 'success'
|
|
&& needs.images-mirror-missing.result == 'success'
|
|
&& (!inputs.chunk_gate
|
|
|| contains(fromJSON('["success","skipped"]'), needs.test-deploy-chunk-0.result)) }}
|
|
needs:
|
|
- lint
|
|
- test
|
|
- build-ci-images
|
|
- images-mirror-missing
|
|
- validate-selection
|
|
- test-deploy-chunk-0
|
|
uses: ./.github/workflows/call-test-deploy.yml
|
|
with:
|
|
distros: ${{ inputs.distros }}
|
|
index: "1"
|
|
sweep: ${{ inputs.sweep != '' && inputs.sweep || github.run_number }}
|
|
offset: ${{ inputs.offset }}
|
|
chunk_size: ${{ inputs.chunk_size }}
|
|
whitelist: ${{ inputs.whitelist }}
|
|
priority: ${{ inputs.priority }}
|
|
lifecycles: ${{ inputs.lifecycles }}
|
|
modes: ${{ inputs.mode }}
|
|
filesystem: ${{ inputs.filesystem }}
|
|
tor: ${{ inputs.tor }}
|
|
vpn: ${{ inputs.vpn }}
|
|
meshes: ${{ inputs.meshes }}
|
|
|
|
test-deploy-chunk-2:
|
|
name: 🧱 Chunk 2
|
|
if: >-
|
|
${{ !cancelled()
|
|
&& inputs.resume_from_chunk <= 2
|
|
&& needs.lint.result == 'success'
|
|
&& needs.test.result == 'success'
|
|
&& needs.build-ci-images.result == 'success'
|
|
&& needs.images-mirror-missing.result == 'success'
|
|
&& (!inputs.chunk_gate
|
|
|| (contains(fromJSON('["success","skipped"]'), needs.test-deploy-chunk-0.result)
|
|
&& contains(fromJSON('["success","skipped"]'), needs.test-deploy-chunk-1.result))) }}
|
|
needs:
|
|
- lint
|
|
- test
|
|
- build-ci-images
|
|
- images-mirror-missing
|
|
- validate-selection
|
|
- test-deploy-chunk-0
|
|
- test-deploy-chunk-1
|
|
uses: ./.github/workflows/call-test-deploy.yml
|
|
with:
|
|
distros: ${{ inputs.distros }}
|
|
index: "2"
|
|
sweep: ${{ inputs.sweep != '' && inputs.sweep || github.run_number }}
|
|
offset: ${{ inputs.offset }}
|
|
chunk_size: ${{ inputs.chunk_size }}
|
|
whitelist: ${{ inputs.whitelist }}
|
|
priority: ${{ inputs.priority }}
|
|
lifecycles: ${{ inputs.lifecycles }}
|
|
modes: ${{ inputs.mode }}
|
|
filesystem: ${{ inputs.filesystem }}
|
|
tor: ${{ inputs.tor }}
|
|
vpn: ${{ inputs.vpn }}
|
|
meshes: ${{ inputs.meshes }}
|
|
|
|
test-deploy-chunk-3:
|
|
name: 🧱 Chunk 3
|
|
if: >-
|
|
${{ !cancelled()
|
|
&& inputs.resume_from_chunk <= 3
|
|
&& needs.lint.result == 'success'
|
|
&& needs.test.result == 'success'
|
|
&& needs.build-ci-images.result == 'success'
|
|
&& needs.images-mirror-missing.result == 'success'
|
|
&& (!inputs.chunk_gate
|
|
|| (contains(fromJSON('["success","skipped"]'), needs.test-deploy-chunk-0.result)
|
|
&& contains(fromJSON('["success","skipped"]'), needs.test-deploy-chunk-1.result)
|
|
&& contains(fromJSON('["success","skipped"]'), needs.test-deploy-chunk-2.result))) }}
|
|
needs:
|
|
- lint
|
|
- test
|
|
- build-ci-images
|
|
- images-mirror-missing
|
|
- validate-selection
|
|
- test-deploy-chunk-0
|
|
- test-deploy-chunk-1
|
|
- test-deploy-chunk-2
|
|
uses: ./.github/workflows/call-test-deploy.yml
|
|
with:
|
|
distros: ${{ inputs.distros }}
|
|
index: "3"
|
|
sweep: ${{ inputs.sweep != '' && inputs.sweep || github.run_number }}
|
|
offset: ${{ inputs.offset }}
|
|
chunk_size: ${{ inputs.chunk_size }}
|
|
whitelist: ${{ inputs.whitelist }}
|
|
priority: ${{ inputs.priority }}
|
|
lifecycles: ${{ inputs.lifecycles }}
|
|
modes: ${{ inputs.mode }}
|
|
filesystem: ${{ inputs.filesystem }}
|
|
tor: ${{ inputs.tor }}
|
|
vpn: ${{ inputs.vpn }}
|
|
meshes: ${{ inputs.meshes }}
|
|
|
|
test-runner-smoke:
|
|
needs:
|
|
- test-deploy-chunk-0
|
|
if: ${{ !cancelled() && vars.CI_SELF_HOSTED_RUNNER_COUNT != '0' && vars.CI_SELF_HOSTED_RUNNER_COUNT != '' && (needs.test-deploy-chunk-0.result == 'success' || needs.test-deploy-chunk-0.result == 'failure') }}
|
|
uses: ./.github/workflows/call-test-runner-smoke.yml
|
|
with:
|
|
ref: ${{ github.sha }}
|
|
secrets: inherit
|
|
|
|
test-install-make:
|
|
needs: validate-selection
|
|
uses: ./.github/workflows/call-test-install-make.yml
|
|
test-install-pkgmgr:
|
|
needs: validate-selection
|
|
uses: ./.github/workflows/call-test-install-pkgmgr.yml
|
|
|
|
test-workspace:
|
|
if: ${{ inputs.workspace == 'true' || (inputs.workspace == 'auto' && (inputs.whitelist == '' || inputs.whitelist == '__ALL__')) }}
|
|
uses: ./.github/workflows/call-test-workspace.yml
|
|
with:
|
|
workspace_track: ${{ inputs.workspace_track }}
|
|
needs:
|
|
- images-mirror-missing
|
|
- lint
|
|
- test
|
|
secrets: inherit
|
|
|
|
report-main-failures:
|
|
name: 🐞 Report main role failures
|
|
needs:
|
|
- test-deploy-chunk-0
|
|
- test-deploy-chunk-1
|
|
- test-deploy-chunk-2
|
|
- test-deploy-chunk-3
|
|
if: ${{ always() && github.ref == 'refs/heads/main' && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')) }}
|
|
runs-on: ubuntu-26.04
|
|
permissions:
|
|
issues: write
|
|
actions: read
|
|
contents: read
|
|
steps:
|
|
- uses: actions/checkout@v7.0.0
|
|
- name: 🐞 Open or append issues for failed roles
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: python3 -m cli.meta.ci.report_failures --run-id "${{ github.run_id }}" --repo "${{ github.repository }}"
|
|
|
|
done:
|
|
name: 🏁 Finish pipeline
|
|
needs:
|
|
- validate-selection
|
|
- build-ci-images
|
|
- images-mirror-missing
|
|
- test-dns
|
|
- test-install-make
|
|
- test-install-pkgmgr
|
|
- test-deploy-chunk-0
|
|
- test-deploy-chunk-1
|
|
- test-deploy-chunk-2
|
|
- test-deploy-chunk-3
|
|
- test-runner-smoke
|
|
- test-workspace
|
|
- test
|
|
- lint
|
|
- security-codeql
|
|
if: ${{ always() }}
|
|
runs-on: ubuntu-26.04
|
|
steps:
|
|
- name: 🛑 Fail if any upstream job failed or was cancelled
|
|
if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}
|
|
run: |
|
|
echo "Pipeline failed: at least one upstream job failed or was cancelled." >&2
|
|
exit 1
|
|
- name: ✅ Done
|
|
run: echo "All checks passed."
|