diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7cfe374..8fa5eb2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,11 +21,11 @@ concurrency: jobs: # docker-build needs a Docker daemon, and cordon-exec has none (Docker on our host is root). So the # image job runs on GitHub's runners, on every push to the default branch and on a PR that - # changes what the image is built from: the Dockerfile, .dockerignore, the package files, + # changes what the image is built from: the Dockerfile, .dockerignore or Dockerfile.dockerignore, the package files, # tsconfig.json, src/, or this file. The required check docker-build runs on our runner and passes only when that # job passed or was not owed, so a PR that changes none of them never waits on GitHub's queue. docker-build-inputs: - runs-on: ${{ github.event.pull_request && (github.event.pull_request.head.repo.full_name != github.repository || github.event.pull_request.user.login == 'dependabot[bot]') && 'ubuntu-latest' || fromJSON('["self-hosted","cordon-exec"]') }} + runs-on: ${{ (github.repository != 'askalf/cordon' || github.event.pull_request && (github.event.pull_request.head.repo.full_name != github.repository || github.event.pull_request.user.login == 'dependabot[bot]')) && 'ubuntu-latest' || fromJSON('["self-hosted","cordon-exec"]') }} timeout-minutes: 5 outputs: changed: ${{ steps.diff.outputs.changed }} @@ -57,7 +57,7 @@ jobs: while IFS= read -r -d '' f; do printf '%s\n' "$f" case "$f" in - Dockerfile|.dockerignore|package.json|package-lock.json|tsconfig.json|src/*|.github/workflows/build.yml) + Dockerfile|.dockerignore|Dockerfile.dockerignore|package.json|package-lock.json|tsconfig.json|src/*|.github/workflows/build.yml) changed=true ;; esac done < "$list" @@ -118,7 +118,7 @@ jobs: docker-build: needs: [docker-build-inputs, docker-build-image] if: always() - runs-on: ${{ github.event.pull_request && (github.event.pull_request.head.repo.full_name != github.repository || github.event.pull_request.user.login == 'dependabot[bot]') && 'ubuntu-latest' || fromJSON('["self-hosted","cordon-exec"]') }} + runs-on: ${{ (github.repository != 'askalf/cordon' || github.event.pull_request && (github.event.pull_request.head.repo.full_name != github.repository || github.event.pull_request.user.login == 'dependabot[bot]')) && 'ubuntu-latest' || fromJSON('["self-hosted","cordon-exec"]') }} timeout-minutes: 5 steps: - name: the image job passed, or no change owed one diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f451f46..6e8a2cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,11 +14,11 @@ concurrency: jobs: test: - # Our own code (pushes, same-repo PRs, schedules, dispatches) runs on cordon-exec, the - # non-root one-job runners on our host, so GitHub's hosted - # queue going down does not hold our PRs. A fork's PR and a Dependabot PR run code nobody - # here wrote, so they stay on GitHub's runners. - runs-on: ${{ github.event.pull_request && (github.event.pull_request.head.repo.full_name != github.repository || github.event.pull_request.user.login == 'dependabot[bot]') && 'ubuntu-latest' || fromJSON('["self-hosted","cordon-exec"]') }} + # Our own code (pushes, same-repo PRs, schedules, dispatches) runs on cordon-exec, our non-root + # one-job runners, so GitHub's hosted queue going down does not hold our PRs. A fork's PR + # and a Dependabot PR run code nobody here wrote, and a fork repository has no cordon-exec + # runners, so those stay on GitHub's runners. + runs-on: ${{ (github.repository != 'askalf/cordon' || github.event.pull_request && (github.event.pull_request.head.repo.full_name != github.repository || github.event.pull_request.user.login == 'dependabot[bot]')) && 'ubuntu-latest' || fromJSON('["self-hosted","cordon-exec"]') }} timeout-minutes: 10 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 7c429b5..e47d4be 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -18,11 +18,9 @@ concurrency: jobs: analyze: name: analyze (${{ matrix.language }}) - # Our own code (pushes, same-repo PRs, schedules, dispatches) runs on cordon-exec, the - # non-root one-job runners on our host, so GitHub's hosted - # queue going down does not hold our PRs. A fork's PR and a Dependabot PR run code nobody - # here wrote, so they stay on GitHub's runners. - runs-on: ${{ github.event.pull_request && (github.event.pull_request.head.repo.full_name != github.repository || github.event.pull_request.user.login == 'dependabot[bot]') && 'ubuntu-latest' || fromJSON('["self-hosted","cordon-exec"]') }} + # CodeQL runs on GitHub's runners: it sizes itself to the machine, and the exec runners of + # every repo share one host, so concurrent analyses would exhaust its memory. + runs-on: ubuntu-latest timeout-minutes: 20 permissions: actions: read @@ -36,16 +34,10 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - # CodeQL sizes itself to the machine's RAM and cores; on our host every exec runner shares - # one memory ceiling, so on our runner it gets 2G and two threads, on GitHub's its defaults. - uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 with: languages: ${{ matrix.language }} queries: security-and-quality - ram: ${{ runner.environment == 'self-hosted' && '2048' || '' }} - threads: ${{ runner.environment == 'self-hosted' && '2' || '' }} - uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 with: category: "/language:${{ matrix.language }}" - ram: ${{ runner.environment == 'self-hosted' && '2048' || '' }} - threads: ${{ runner.environment == 'self-hosted' && '2' || '' }} diff --git a/.github/workflows/fleet-status-backfill.yml b/.github/workflows/fleet-status-backfill.yml index 8b36828..f0310f7 100644 --- a/.github/workflows/fleet-status-backfill.yml +++ b/.github/workflows/fleet-status-backfill.yml @@ -11,7 +11,8 @@ jobs: backfill: # Our own script on its own trigger, never PR code: on our runner, so GitHub's hosted # queue going down does not stop it. - runs-on: [self-hosted, cordon-exec] + # A fork repository has no cordon-exec runners, so there it runs on GitHub's. + runs-on: ${{ github.repository == 'askalf/cordon' && fromJSON('["self-hosted","cordon-exec"]') || 'ubuntu-latest' }} timeout-minutes: 10 permissions: contents: read diff --git a/.github/workflows/fleet-status-self-test.yml b/.github/workflows/fleet-status-self-test.yml index 0d0ec44..1fc2cb9 100644 --- a/.github/workflows/fleet-status-self-test.yml +++ b/.github/workflows/fleet-status-self-test.yml @@ -16,11 +16,11 @@ permissions: {} jobs: self-test: - # Our own code (pushes, same-repo PRs, schedules, dispatches) runs on cordon-exec, the - # non-root one-job runners on our host, so GitHub's hosted - # queue going down does not hold our PRs. A fork's PR and a Dependabot PR run code nobody - # here wrote, so they stay on GitHub's runners. - runs-on: ${{ github.event.pull_request && (github.event.pull_request.head.repo.full_name != github.repository || github.event.pull_request.user.login == 'dependabot[bot]') && 'ubuntu-latest' || fromJSON('["self-hosted","cordon-exec"]') }} + # Our own code (pushes, same-repo PRs, schedules, dispatches) runs on cordon-exec, our non-root + # one-job runners, so GitHub's hosted queue going down does not hold our PRs. A fork's PR + # and a Dependabot PR run code nobody here wrote, and a fork repository has no cordon-exec + # runners, so those stay on GitHub's runners. + runs-on: ${{ (github.repository != 'askalf/cordon' || github.event.pull_request && (github.event.pull_request.head.repo.full_name != github.repository || github.event.pull_request.user.login == 'dependabot[bot]')) && 'ubuntu-latest' || fromJSON('["self-hosted","cordon-exec"]') }} timeout-minutes: 5 permissions: contents: read diff --git a/.github/workflows/fleet-status.yml b/.github/workflows/fleet-status.yml index 7205a7b..17aef32 100644 --- a/.github/workflows/fleet-status.yml +++ b/.github/workflows/fleet-status.yml @@ -44,7 +44,8 @@ jobs: # On our runner for every event, a fork's included: the job runs the default branch's script # and never PR code (see above), so GitHub's hosted queue going down does not hold the # fleet/verify and fleet/review statuses a merge waits on. - runs-on: [self-hosted, cordon-exec] + # A fork repository has no cordon-exec runners, so there it runs on GitHub's. + runs-on: ${{ github.repository == 'askalf/cordon' && fromJSON('["self-hosted","cordon-exec"]') || 'ubuntu-latest' }} timeout-minutes: 5 permissions: contents: read diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index 6ad6902..3d9aa6a 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -32,11 +32,11 @@ permissions: jobs: manifest: name: manifest matches the repo - # Our own code (pushes, same-repo PRs, schedules, dispatches) runs on cordon-exec, the - # non-root one-job runners on our host, so GitHub's hosted - # queue going down does not hold our PRs. A fork's PR and a Dependabot PR run code nobody - # here wrote, so they stay on GitHub's runners. - runs-on: ${{ github.event.pull_request && (github.event.pull_request.head.repo.full_name != github.repository || github.event.pull_request.user.login == 'dependabot[bot]') && 'ubuntu-latest' || fromJSON('["self-hosted","cordon-exec"]') }} + # Our own code (pushes, same-repo PRs, schedules, dispatches) runs on cordon-exec, our non-root + # one-job runners, so GitHub's hosted queue going down does not hold our PRs. A fork's PR + # and a Dependabot PR run code nobody here wrote, and a fork repository has no cordon-exec + # runners, so those stay on GitHub's runners. + runs-on: ${{ (github.repository != 'askalf/cordon' || github.event.pull_request && (github.event.pull_request.head.repo.full_name != github.repository || github.event.pull_request.user.login == 'dependabot[bot]')) && 'ubuntu-latest' || fromJSON('["self-hosted","cordon-exec"]') }} timeout-minutes: 3 permissions: contents: read diff --git a/scripts/fleet-status.test.mjs b/scripts/fleet-status.test.mjs index 30e8e29..7e43977 100644 --- a/scripts/fleet-status.test.mjs +++ b/scripts/fleet-status.test.mjs @@ -514,40 +514,49 @@ console.log('\n fleet-status.yml: which events run the job for a fork'); && checkouts.every((m) => /ref: \$\{\{ github\.event\.repository\.default_branch \}\}/.test(m[1]))); check('no step reads the PR head ref or sha', !/(pull_request\.head\.(ref|sha)|workflow_run\.head_sha)/.test(own)); - // Our own code runs on cordon-exec, our host's runners; code nobody here wrote never does. A - // runs-on expression sends a fork's PR and a Dependabot PR to GitHub's runners and everything - // else to ours. A literal cordon-exec on a pull_request workflow needs a job if: that keeps forks off - // it, or a job that never runs PR code (fleet-status, checked above). + // Our own code runs on cordon-exec, our host's runners; code nobody here wrote never does, and a fork + // repository, which has no cordon-exec runners, never waits for one. The own-code expression sends a + // fork's PR, a Dependabot PR and any run in a fork repository to GitHub's runners and everything + // else to ours. The repository-only expression, for jobs that never run PR code, sends everything + // here to ours. A literal cordon-exec needs a job if: that keeps it to this repository's own code. const OWN_RE = /^\s+runs-on: \$\{\{ (.+) \}\}$/; - const exprs = []; + const HOME_REPO = 'askalf/cordon'; + const ownExprs = []; + const repoOnly = []; const literal = []; for (const f of readdirSync(dir).filter((x) => /\.ya?ml$/.test(x))) { const y = readFileSync(join(dir, f), 'utf8'); for (const line of y.split('\n')) { const m = OWN_RE.exec(line); - if (m && m[1].includes('cordon-exec')) exprs.push({ f, e: m[1] }); - else if (/^\s+runs-on: \[self-hosted, cordon-exec\]/.test(line)) literal.push(f); + if (m && m[1].includes('cordon-exec')) (m[1].includes('github.event.pull_request') ? ownExprs : repoOnly).push({ f, e: m[1] }); + else if (/^\s+runs-on: \[self-hosted, cordon\-exec\]/.test(line)) literal.push(f); } } - check('the own-code runs-on expression is in use', exprs.length >= 6); - const hosted = (e, github) => evalIf(`(${e}) == 'ubuntu-latest'`, { github: { repository: REPO, ...github } }); + check('the own-code runs-on expression is in use', ownExprs.length >= 5); + const hosted = (e, github, repository = HOME_REPO) => evalIf(`(${e}) == 'ubuntu-latest'`, { github: { repository, ...github } }); const prFrom = (event_name, headRepo, login = 'askalf') => ({ event_name, event: { pull_request: { head: { repo: { full_name: headRepo } }, user: { login } } } }); - for (const { f, e } of exprs) { + const EVENTS = [{ event_name: 'push', event: {} }, { event_name: 'schedule', event: {} }, { event_name: 'workflow_dispatch', event: {} }]; + const FORK_REPO = 'someone/cordon'; + for (const { f, e } of ownExprs) { check(`${f}: a fork's pull_request runs on GitHub's runners`, hosted(e, prFrom('pull_request', FORKED))); check(`${f}: a fork's pull_request_review runs on GitHub's runners`, hosted(e, prFrom('pull_request_review', FORKED))); - check(`${f}: a Dependabot PR runs on GitHub's runners`, hosted(e, prFrom('pull_request', REPO, 'dependabot[bot]'))); - check(`${f}: a same-repo PR runs on ours`, !hosted(e, prFrom('pull_request', REPO))); - check(`${f}: a push, schedule or dispatch runs on ours`, - !hosted(e, { event_name: 'push', event: {} }) && !hosted(e, { event_name: 'schedule', event: {} }) - && !hosted(e, { event_name: 'workflow_dispatch', event: {} })); + check(`${f}: a Dependabot PR runs on GitHub's runners`, hosted(e, prFrom('pull_request', HOME_REPO, 'dependabot[bot]'))); + check(`${f}: a same-repo PR runs on ours`, !hosted(e, prFrom('pull_request', HOME_REPO))); + check(`${f}: a push, schedule or dispatch runs on ours`, EVENTS.every((ev) => !hosted(e, ev))); + check(`${f}: in a fork repository every event runs on GitHub's runners`, + EVENTS.every((ev) => hosted(e, ev, FORK_REPO)) && hosted(e, prFrom('pull_request', FORK_REPO), FORK_REPO)); check(`${f}: the expression names exactly our label`, e.includes(`fromJSON('["self-hosted","cordon-exec"]')`)); } + for (const { f, e } of repoOnly) { + check(`${f}: every event here runs on ours`, EVENTS.every((ev) => !hosted(e, ev)) && !hosted(e, prFrom('pull_request', FORKED))); + check(`${f}: in a fork repository it runs on GitHub's runners`, EVENTS.every((ev) => hosted(e, ev, FORK_REPO))); + check(`${f}: only a job that never runs PR code uses it`, ['fleet-status.yml', 'fleet-status-backfill.yml'].includes(f)); + } for (const f of literal) { const y = readFileSync(join(dir, f), 'utf8'); - const prTriggered = /\bpull_request(_target|_review)?\b/.test(onBlockOf(y)); - check(`${f}: a literal cordon-exec is on a workflow no fork can run, or keeps forks off it`, - !prTriggered || f === 'fleet-status.yml' || /head\.repo\.full_name == github\.repository/.test(y)); + check(`${f}: a literal cordon-exec job runs only in this repository, or only on its own PRs`, + y.includes(`github.repository == '${HOME_REPO}'`) || /head\.repo\.full_name == github\.repository/.test(y)); } let relay = '';