diff --git a/.github/workflows/release-studio.yaml b/.github/workflows/release-studio.yaml index db2b527188..328c02ecc7 100644 --- a/.github/workflows/release-studio.yaml +++ b/.github/workflows/release-studio.yaml @@ -28,10 +28,36 @@ env: IMAGE_NAME: ${{ github.repository }}/studio NGINX_IMAGE_NAME: ${{ github.repository }}/studio-nginx +# Runner selection, driven by repository variables so the release path can be +# moved onto self-hosted runners — and moved back — without a PR. All three +# default to the hosted runners this workflow has always used, so an unset +# variable is a no-op. +# +# CI_RUNNER_RELEASE non-docker jobs (build-dist, publish-npm, +# release, bump-deco-apps-cd, notify-docs-agent) +# CI_RUNNER_RELEASE_DIND docker jobs, linux/amd64 +# CI_RUNNER_RELEASE_DIND_ARM64 docker jobs, linux/arm64 +# +# Deliberately SEPARATE from CI_RUNNER_LINUX in test.yml. The risk is +# asymmetric: a stuck PR check annoys one author, a stuck release blocks +# shipping. Prove the checks on self-hosted for a week before touching these. +# +# The arm64 variable exists because this matrix builds arm64 NATIVELY on +# ubuntu-24.04-arm rather than under QEMU. Pointing that leg at an amd64 +# runner would silently regress it to emulation and roughly triple the build, +# so it must resolve to a genuinely arm64 scale set or stay hosted. +# +# `prepare` is grouped with the docker jobs, not the light ones: it runs +# docker/login-action and `docker manifest inspect`, which need the docker CLI +# that the stock ARC runner image only has wired up in dind mode. +# +# Rollback for any of them: `gh variable delete --repo decocms/studio`, +# effective on the next run. + jobs: prepare: name: Prepare release metadata - runs-on: ubuntu-latest + runs-on: ${{ vars.CI_RUNNER_RELEASE_DIND || 'ubuntu-latest' }} # Releases are cut by the [release]: version-bump commit that release-tagging # pushes to main (the bot push uses the App token, which re-triggers # workflows). We run on EVERY push here — a non-bump push just reads an @@ -114,7 +140,7 @@ jobs: needs.prepare.outputs.version-changed == 'true' || needs.prepare.outputs.image-exists == 'false' || needs.prepare.outputs.nginx-image-exists == 'false' - runs-on: ubuntu-latest + runs-on: ${{ vars.CI_RUNNER_RELEASE || 'ubuntu-latest' }} permissions: contents: read steps: @@ -174,7 +200,7 @@ jobs: name: Publish to NPM needs: [prepare, build-dist] if: needs.prepare.outputs.version-changed == 'true' - runs-on: ubuntu-latest + runs-on: ${{ vars.CI_RUNNER_RELEASE || 'ubuntu-latest' }} permissions: contents: read id-token: write @@ -221,10 +247,10 @@ jobs: include: - arch: amd64 platform: linux/amd64 - runner: ubuntu-latest + runner: ${{ vars.CI_RUNNER_RELEASE_DIND || 'ubuntu-latest' }} - arch: arm64 platform: linux/arm64 - runner: ubuntu-24.04-arm + runner: ${{ vars.CI_RUNNER_RELEASE_DIND_ARM64 || 'ubuntu-24.04-arm' }} steps: - uses: actions/checkout@v4 with: @@ -296,10 +322,10 @@ jobs: include: - arch: amd64 platform: linux/amd64 - runner: ubuntu-latest + runner: ${{ vars.CI_RUNNER_RELEASE_DIND || 'ubuntu-latest' }} - arch: arm64 platform: linux/arm64 - runner: ubuntu-24.04-arm + runner: ${{ vars.CI_RUNNER_RELEASE_DIND_ARM64 || 'ubuntu-24.04-arm' }} steps: - uses: actions/checkout@v4 with: @@ -360,7 +386,7 @@ jobs: always() && needs.prepare.outputs.image-exists == 'false' && needs.build-docker.result == 'success' - runs-on: ubuntu-latest + runs-on: ${{ vars.CI_RUNNER_RELEASE_DIND || 'ubuntu-latest' }} permissions: contents: read packages: write @@ -419,7 +445,7 @@ jobs: always() && needs.prepare.outputs.nginx-image-exists == 'false' && needs.build-nginx-docker.result == 'success' - runs-on: ubuntu-latest + runs-on: ${{ vars.CI_RUNNER_RELEASE_DIND || 'ubuntu-latest' }} permissions: contents: read packages: write @@ -485,7 +511,7 @@ jobs: (needs.publish-npm.result == 'success' || needs.publish-npm.result == 'skipped') && (needs.merge-docker.result == 'success' || needs.merge-docker.result == 'skipped') && (needs.merge-nginx-docker.result == 'success' || needs.merge-nginx-docker.result == 'skipped') - runs-on: ubuntu-latest + runs-on: ${{ vars.CI_RUNNER_RELEASE || 'ubuntu-latest' }} permissions: contents: write steps: @@ -578,7 +604,7 @@ jobs: needs.merge-nginx-docker.result == 'success' ) ) - runs-on: ubuntu-latest + runs-on: ${{ vars.CI_RUNNER_RELEASE || 'ubuntu-latest' }} # Reuses DEPLOY_REPO_TOKEN — the decobot machine-user PAT already used to # dispatch the Studio image bump (non-personal; decobot is admin on # decocms/deco-apps-cd). Same bot, same mechanism as the legacy repo, no @@ -633,7 +659,7 @@ jobs: name: Notify docs agent needs: [prepare, release] if: always() && needs.release.result == 'success' - runs-on: ubuntu-latest + runs-on: ${{ vars.CI_RUNNER_RELEASE || 'ubuntu-latest' }} env: WEBHOOK_URL: ${{ vars.DOCS_AGENT_WEBHOOK_URL }} WEBHOOK_TOKEN: ${{ secrets.DOCS_AGENT_WEBHOOK_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0f4142b01b..1ed615088c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,6 +23,35 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} +# Runner selection for the four cheap-but-queued jobs below (format, lint, +# typecheck, test). Resolved from a repository variable so the pilot can be +# switched on and rolled back WITHOUT a PR: +# +# on: gh variable set CI_RUNNER_LINUX --body deco-linux-amd64 --repo decocms/studio +# off: gh variable delete CI_RUNNER_LINUX --repo decocms/studio +# +# Unset resolves to ubuntu-latest, so this file is a no-op until someone sets +# the variable. That matters because these are required checks: if the +# self-hosted scale set is unavailable, jobs targeting its label sit queued +# until GitHub's 24h timeout rather than failing fast, and nothing merges. +# Deleting the variable restores hosted runners on the next run. +# +# Why these five: they have waited up to ~90 min at the p99 (hosted +# concurrency ceiling saturating at ~915 runs/day) for 0.4-2 min of work. They +# also need nothing beyond checkout + setup-bun, so they run on the stock ARC +# runner image — no sudo, no docker, no privileged sidecar. +# +# Two jobs stay hosted: +# changes 1 min, and it gates the rest — keeping it hosted means +# a scale-set outage still produces a run that reports. +# web-component-tests `playwright install --with-deps` / `install-deps` +# shell out to sudo apt-get, which the stock runner +# image has no root for. Needs a custom image first. +# +# Inlined per job rather than hoisted into `env:` because `runs-on` cannot read +# the `env` context — only `github`, `needs`, `strategy`, `matrix`, `inputs` +# and `vars`. + jobs: # Gate: skip the (required) check jobs when a PR only touches workflow YAML, # docs, or markdown. Required checks can't be skipped via workflow-level @@ -88,7 +117,7 @@ jobs: format: needs: changes if: needs.changes.outputs.relevant == 'true' - runs-on: ubuntu-latest + runs-on: ${{ vars.CI_RUNNER_LINUX || 'ubuntu-latest' }} steps: - name: Checkout repository uses: actions/checkout@v4 @@ -102,7 +131,7 @@ jobs: lint: needs: changes if: needs.changes.outputs.relevant == 'true' - runs-on: ubuntu-latest + runs-on: ${{ vars.CI_RUNNER_LINUX || 'ubuntu-latest' }} steps: - name: Checkout repository uses: actions/checkout@v4 @@ -121,7 +150,7 @@ jobs: typecheck: needs: changes if: needs.changes.outputs.relevant == 'true' - runs-on: ubuntu-latest + runs-on: ${{ vars.CI_RUNNER_LINUX || 'ubuntu-latest' }} steps: - name: Checkout repository uses: actions/checkout@v4 @@ -140,7 +169,7 @@ jobs: test: needs: changes if: needs.changes.outputs.relevant == 'true' - runs-on: ubuntu-latest + runs-on: ${{ vars.CI_RUNNER_LINUX || 'ubuntu-latest' }} # The suite normally finishes in ~1 min. A single hung file under the # parallel runner stalls the whole job silently (per-file output is # buffered, so the culprit prints nothing) — without this bound that's @@ -162,7 +191,7 @@ jobs: build: needs: changes if: needs.changes.outputs.relevant == 'true' - runs-on: ubuntu-latest + runs-on: ${{ vars.CI_RUNNER_LINUX || 'ubuntu-latest' }} steps: - name: Checkout repository uses: actions/checkout@v4