From d61bff797f565f5fb38d88088fa6382c541eeddb Mon Sep 17 00:00:00 2001 From: "Wayne E. Seguin" Date: Wed, 9 Sep 2026 15:26:32 -0400 Subject: [PATCH] Run CI on GitHub's hosted runners Standard hosted runners are free and unmetered for public repositories, so there is nothing to gain by pointing this repository at the lab runners, and something real to lose: SHIELD has 66 forks, and a fork pull request on a self-hosted runner runs a stranger's code on our own hardware. Moving to ubuntu-latest drops the machinery the lab runners needed. The golang container, the bind-mounted module and build caches, the system-level safe.directory write, and the GOMAXPROCS cap all existed to work around a persistent shared host, and a fresh hosted runner has none of those problems. --- .github/dependabot.yml | 6 +- .github/workflows/ci.yml | 160 ++++++++++----------------------------- 2 files changed, 43 insertions(+), 123 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 275f5120c..45e3b63d4 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -12,9 +12,9 @@ updates: commit-message: prefix: deps # SHIELD links the AWS, Google, Azure and Docker SDKs, so ungrouped - # updates open a queue of PRs every week and each one costs a full CI - # chain on the lab runners. One PR carries every patch and minor bump; - # majors stay separate, since those want reading. + # updates would open a queue of pull requests every week, each one asking + # for the same glance. One pull request carries every patch and minor + # bump; majors stay separate, since those want reading. groups: go-minor-patch: update-types: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index abe5be171..3b23c1961 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,81 +9,45 @@ on: permissions: contents: read -# The lab runners are a small, shared pool, so a superseded pull-request run -# would keep them busy long after its result stopped mattering. Cancel it when -# new commits arrive, and never cancel a run on develop or main. +# A superseded pull-request run tells us nothing once new commits arrive, so +# cancel it. Runs on develop and main are never cancelled: those are the ones +# whose result we go back and read. concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} -# Each lab runner VM hosts four jobs on eight cores. Go sizes its build -# parallelism from the host core count, so without this every job fans out as -# if it owned the machine and four of them together exhaust the VM's memory. -# Runner-level env does not cross into container jobs; workflow env is part of -# the job context, which the runner does carry in. -env: - GOMAXPROCS: 2 - jobs: - # Every job runs in a golang container. The self-hosted runner host is - # deliberately minimal, and mixing host jobs with container jobs leaves the - # shared workspace owned by two different uids (the host runner user and - # container root), which breaks checkout cleanup and trips git's - # dubious-ownership guard. - # - # The "Trust the checkout" step writes safe.directory at --system level - # (/etc/gitconfig). The checkout action writes it under its own HOME, which - # the shell steps in the container do not share, so git run by go build's - # VCS stamping would otherwise exit 128 on the runner-user-owned .git. + # Every job runs on GitHub's standard hosted runners, which are free and + # unmetered for public repositories. That also keeps fork pull requests off + # our own hardware: this repository has dozens of forks, and a self-hosted + # runner would be executing their code. # - # Actions are pinned to full commit SHAs, since tags move and these jobs run - # on a lab runner; Dependabot's github-actions ecosystem keeps the pins - # current. persist-credentials is off everywhere: no job pushes, and the - # workspace survives between jobs on this runner. + # Actions are pinned to full commit SHAs, since tags move under you. + # Dependabot's github-actions ecosystem updates the SHA and its trailing + # version comment together, which is what makes the pins maintainable. # - # timeout-minutes is set on every job, because a hung job would otherwise - # hold a runner for GitHub's six-hour default. + # persist-credentials is off everywhere: no job pushes. # - # The Go module and build caches are host directories bind-mounted into each - # container rather than the Actions cache service. The container filesystem - # is fresh every job but the runner is persistent, so fetching and unpacking - # a multi-GB cache.tzst costs more than the build it saves, and it untars - # over Go's read-only module cache. setup-go's own cache stays off for the - # same reason. The mounts live outside _work, so the runner's workspace - # reclaim hook leaves them alone. GOCACHE is set explicitly because the - # runner overrides HOME to /github/home inside the container, which lives - # under _work/_temp and is wiped between jobs. + # timeout-minutes is set on every job so a hung one cannot burn through + # GitHub's six-hour default. lint: name: Lint - runs-on: self-hosted - container: - image: golang:1.27.1 - volumes: - - /home/runner/gha-container-cache/go:/go - - /home/runner/gha-container-cache/go-build:/root/.cache/go-build - env: - GOCACHE: /root/.cache/go-build - timeout-minutes: 30 + runs-on: ubuntu-latest + timeout-minutes: 20 steps: - name: Checkout code uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - name: Trust the checkout despite mixed file owners - run: git config --system --add safe.directory "$GITHUB_WORKSPACE" - - name: Set up Go uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version: '1.27.1' - # The mounted /go volume is the module cache; the action's own cache - # would restore a second copy over it and fail on tar. - cache: false # gofmt is checked over the tracked sources only. The vendor tree is # third-party code we do not reformat, and `make format` would rewrite - # files rather than report them. + # the files rather than report them. - name: Check gofmt run: | unformatted=$(gofmt -l $(git ls-files '*.go' | grep -v '^vendor/')) @@ -98,33 +62,22 @@ jobs: test: name: Test - runs-on: self-hosted - container: - image: golang:1.27.1 - volumes: - - /home/runner/gha-container-cache/go:/go - - /home/runner/gha-container-cache/go-build:/root/.cache/go-build - env: - GOCACHE: /root/.cache/go-build - timeout-minutes: 30 + runs-on: ubuntu-latest + timeout-minutes: 20 steps: - name: Checkout code uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - name: Trust the checkout despite mixed file owners - run: git config --system --add safe.directory "$GITHUB_WORKSPACE" - - name: Set up Go uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version: '1.27.1' - cache: false # `make go-tests` builds the daemon, agent, schema, crypt and report # binaries first and then runs `go test -race` with the repository root - # and bin/ on PATH. The agent suite shells out to shield-pipe, so the + # and bin/ on PATH. The agent suite shells out to shield-pipe, so those # binaries have to exist before the tests run; calling `go test` on its # own leaves five agent specs failing with exit status 127. - name: Run unit tests with the race detector @@ -132,15 +85,8 @@ jobs: build: name: Build - runs-on: self-hosted - container: - image: golang:1.27.1 - volumes: - - /home/runner/gha-container-cache/go:/go - - /home/runner/gha-container-cache/go-build:/root/.cache/go-build - env: - GOCACHE: /root/.cache/go-build - timeout-minutes: 30 + runs-on: ubuntu-latest + timeout-minutes: 20 needs: [lint, test] steps: - name: Checkout code @@ -148,19 +94,15 @@ jobs: with: persist-credentials: false - - name: Trust the checkout despite mixed file owners - run: git config --system --add safe.directory "$GITHUB_WORKSPACE" - - name: Set up Go uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version: '1.27.1' - cache: false - # The repository vendors its dependencies, so the build must not reach - # the network. `go mod verify` checks the module cache against go.sum, - # and building with -mod=vendor proves the vendor tree is complete and - # consistent with go.mod. + # The repository vendors its dependencies, so the build must not need to + # reach the network. `go mod verify` checks the module cache against + # go.sum, and building with -mod=vendor proves the vendor tree is + # complete and consistent with go.mod. - name: Verify vendored dependencies run: | go mod verify @@ -171,15 +113,8 @@ jobs: plugin-tests: name: Plugin Tests - runs-on: self-hosted - container: - image: golang:1.27.1 - volumes: - - /home/runner/gha-container-cache/go:/go - - /home/runner/gha-container-cache/go-build:/root/.cache/go-build - env: - GOCACHE: /root/.cache/go-build - timeout-minutes: 30 + runs-on: ubuntu-latest + timeout-minutes: 20 needs: [lint, test] # Eight of the 134 plugin specs in t/plugins already fail on develop: they # assert the compact "USAGE: ..." help that the plugin framework printed @@ -194,65 +129,50 @@ jobs: with: persist-credentials: false - - name: Trust the checkout despite mixed file owners - run: git config --system --add safe.directory "$GITHUB_WORKSPACE" - - name: Set up Go uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version: '1.27.1' - cache: false - name: Run plugin tests run: make plugin-tests security: name: Security Scan - runs-on: self-hosted - container: - image: golang:1.27.1 - volumes: - - /home/runner/gha-container-cache/go:/go - - /home/runner/gha-container-cache/go-build:/root/.cache/go-build - env: - GOCACHE: /root/.cache/go-build - timeout-minutes: 30 + runs-on: ubuntu-latest + timeout-minutes: 20 steps: - name: Checkout code uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - name: Trust the checkout despite mixed file owners - run: git config --system --add safe.directory "$GITHUB_WORKSPACE" - - name: Set up Go uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version: '1.27.1' - cache: false # govulncheck reports only the advisories whose vulnerable symbols this - # code actually reaches, so it is the gate that can stay red-means-broken - # rather than red-means-someone-published-a-CVE. + # code actually reaches, so it is the gate that can stay + # red-means-broken rather than red-means-someone-published-a-CVE. - name: Run govulncheck run: | go install golang.org/x/vuln/cmd/govulncheck@latest "$(go env GOPATH)/bin/govulncheck" ./... - # Installed from the pinned release rather than through trivy-action: the - # action's own installer resolves "latest" unless pinned twice, and a - # plain binary drop keeps this job's moving parts visible. + # Installed from the pinned release rather than through trivy-action: + # the action's own installer resolves "latest" unless pinned twice, and + # a plain binary drop keeps this job's moving parts visible. - name: Install trivy run: | curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh \ - | sh -s -- -b /usr/local/bin v0.74.0 + | sudo sh -s -- -b /usr/local/bin v0.74.0 - # Gating on HIGH and CRITICAL only, so an unfixable low-severity advisory - # cannot hold CI red with nothing to remediate. The vendor tree is - # skipped for misconfiguration scanning: it carries upstream projects' - # own Dockerfiles, which we neither build nor ship. Suppressions with - # their justifications live in .trivyignore. + # Gating on HIGH and CRITICAL only, so an unfixable low-severity + # advisory cannot hold CI red with nothing to remediate. The vendor tree + # is skipped for misconfiguration scanning: it carries upstream + # projects' own Dockerfiles, which we neither build nor ship. + # Suppressions with their justifications live in .trivyignore. - name: Scan repository run: >- trivy fs