Skip to content

release: v0.1.226 #3286

release: v0.1.226

release: v0.1.226 #3286

Workflow file for this run

name: Go CLI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
# The merge queue builds each entry on a `gh-readonly-queue/main/...` branch
# and fires `merge_group`, not `pull_request`. Without this trigger the five
# required gates below never report on a queued entry, and every PR is
# ejected when `check_response_timeout_minutes` expires. `lint` stays
# pull-request-only on purpose: only-new-issues has no diff baseline in a
# merge group, and it is not a required context.
merge_group:
concurrency:
# Never cancel a merge-queue run. Cancelling one leaves its required checks
# non-passing, which ejects the entry from the queue -- and two groups built
# on the same base commit share a `gh-readonly-queue/...` ref, so a blanket
# cancel-in-progress can kill a live entry. Superseded pushes to a PR branch
# are still worth cancelling.
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
quality:
name: ${{ matrix.gate }}
runs-on: ubuntu-latest
# Per-gate, because a merge-queue group is heavier than any single PR: it
# stacks up to `max_entries_to_merge` PRs on one branch, so `race` runs the
# whole batch's tests. A flat 8 minutes left it no headroom -- it was killed
# at 8m00s in merge group pr-807-c93b617c, and a timed-out job reports as
# `cancelled`, which is not a passing required check, so the entry was
# silently ejected from the queue with nothing marked failed. The coverage
# command's own package alarm still expires first, so a genuine hang arrives
# as a stack trace rather than a cancellation.
timeout-minutes: ${{ matrix.timeout_minutes }}
# `conformance_rows` is the set of docs/supported-matrix.md rows this gate
# claims to prove. A claimed row's tests can no longer skip themselves when
# the backend is missing, and the always-run receipt check below fails if a
# claimed row never ran.
env:
CODEFLY_CONFORMANCE_REQUIRED: ${{ matrix.conformance_rows }}
CODEFLY_CONFORMANCE_RECEIPTS: ${{ github.workspace }}/conformance-receipts
strategy:
fail-fast: true
matrix:
include:
- gate: bootstrap-audit
timeout_minutes: 8
- gate: control-integration
timeout_minutes: 12
conformance_rows: linux-amd64-native-control
- gate: coverage
timeout_minutes: 16
conformance_rows: linux-amd64-source,linux-amd64-native-npm
- gate: race
timeout_minutes: 20
conformance_rows: linux-amd64-source,linux-amd64-native-npm
steps:
- name: checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Go
if: matrix.gate == 'coverage' || matrix.gate == 'race'
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: go.mod
cache: false
# pkg/librarystore's npm backend shells out to the real npm CLI (npm pack,
# npm publish) against an httptest registry rather than mocking the store;
# those tests need node/npm on the runner.
- name: Set up Node
if: matrix.gate == 'coverage' || matrix.gate == 'race'
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
# Restore before the bootstrap action: that action compiles the CLI, so
# restoring afterward would make its cache permanently ineffective.
# Cache only compiled artifacts. Caching GOMODCACHE alongside a Go
# toolchain directive can race setup-go's toolchain install and produce
# tar restore conflicts.
- name: Restore Go build cache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/go-build
key: cli-go-build-v3-${{ matrix.gate }}-${{ runner.os }}-${{ hashFiles('go.sum') }}
restore-keys: |
cli-go-build-v3-${{ matrix.gate }}-${{ runner.os }}-
# This is both the canonical bootstrap and the cross-repository action
# contract consumed by Codefly products. Dogfood the local action so a
# release cannot publish an installer that CI never exercised.
- name: install Codefly
if: matrix.gate == 'bootstrap-audit' || matrix.gate == 'control-integration'
uses: ./.github/actions/install
- name: Install Dependencies
run: go mod download
# govulncheck gate via the CLI (replaces scripts/govulncheck.sh):
# suppressions + staleness from .govulncheck.yaml, fails only on
# actionable (upstream-fixed) findings.
- name: govulncheck audit
if: matrix.gate == 'bootstrap-audit'
run: |
go install golang.org/x/vuln/cmd/govulncheck@v1.8.0
codefly audit go --dir .
- name: Test with coverage
if: matrix.gate == 'coverage'
env:
CODEFLY_COMPOSITION_OCI_QUALIFY: "1"
run: go test -failfast -v -timeout=4m ./... -coverprofile=cover.out -covermode=atomic -coverpkg=./...
- name: Check test coverage
if: matrix.gate == 'coverage'
id: coverage
uses: vladopajic/go-test-coverage@f484eec846448c97c777a26f8b58c5a69a327e12 # v2
with:
config: ./.testcoverage.yaml
# The CGO-free companion/self-cross build (`-tags codefly_nosemantic`,
# CGO_ENABLED=0) selects pkg/engine/source_nosemantic.go. `codefly
# companion publish` exercises it only in companions.yaml, which needs
# Docker; verify here, unconditionally, that the variant both links
# statically and runs its base source behavior. Without this a regression
# in the analyzer-free variant would surface only at publish time.
- name: Verify CGO-free (companion) build variant
if: matrix.gate == 'coverage'
env:
CGO_ENABLED: "0"
run: |
go build -tags codefly_nosemantic -ldflags '-s -w -extldflags "-static"' -o /dev/null .
go test -tags codefly_nosemantic ./pkg/engine/ -run '^TestNewSourceExecutesLanguageNeutralOperation$' -count=1
- name: Test with race detection
if: matrix.gate == 'race'
env:
CODEFLY_COMPOSITION_OCI_QUALIFY: "1"
run: go test -failfast -race ./... -v
# The cross-process control-ownership proof drives the CLI built by the
# install action above as a real subprocess against real sockets. It runs
# here rather than in the default lanes because those would have to link
# a CLI binary of their own; it needs neither Nix nor an agent, so it runs
# before both and fails fast.
- name: Run the cross-process control-ownership proof
if: matrix.gate == 'control-integration'
run: go test -tags=integration -run '^TestRunRefusesAControlAddressAnotherProcessOwns$|^TestConcurrentDisposableRunsClaimDistinctIdentities$' ./cmd/run -v
- name: Run the in-process run-profile proof
if: matrix.gate == 'control-integration'
run: go test -tags=integration -run '^TestRunProfilesThroughProtocolPeers$' ./pkg/control -v
# Pull separately from the proof below so a cold-cache image fetch is
# attributed to its own step instead of inflating the test's wall time —
# this gate shares one 8-minute job budget with the Nix install above.
# Keep the tag in step with teardownPostgresImage in
# pkg/orchestration/teardown_postgres_integration_test.go.
- name: Pull the ordered-teardown proof's database image
if: matrix.gate == 'control-integration'
run: docker pull mirror.gcr.io/library/postgres:16-alpine
# Ordered teardown (F09): the consumer holds its drain open and commits a
# final transaction to a disposable Postgres; the database must not begin
# stopping until that commit returned. Needs Docker, which the runner has.
# It claims no conformance row: the ordering contract is backend-neutral,
# so it proves nothing about a docs/supported-matrix.md row.
- name: Run the ordered-teardown acceptance proof
if: matrix.gate == 'control-integration'
run: go test -tags=integration -run '^TestFlowStopHoldsPostgresUntilItsConsumerCommits$' ./pkg/orchestration -v
- name: Verify CI image prerequisite ordering
if: matrix.gate == 'control-integration'
run: go test -tags=integration -run '^TestCIBuildOrdersRealImagePrerequisites$' ./cmd/ci -v
# A Go image build reads its modules from the proxy the host prefetched,
# with no credential in the build: it builds from the proxy and fails
# without it. Needs Docker and the host go toolchain, which the runner has.
- name: Prove a Go image build needs no credential with its prefetched modules
if: matrix.gate == 'control-integration'
run: go test -tags=integration -run '^TestAGoModuleBuildNeedsNoCredentialWithThePrefetchedProxy$' ./pkg/orchestration -v
# Two services of one flow pinning one private repository at two
# pseudo-versions: the prefetch deepens a shallow clone, and git's own
# detached auto-maintenance must not repack it under that fetch (cli#886).
# Needs git and the host go toolchain; the repository is local, no network.
- name: Prove one repository at two pseudo-versions fetches
if: matrix.gate == 'control-integration'
run: go test -tags=integration -run '^TestTwoServicesPinningOneRepositoryFetchWithoutGitMaintainingTheCache$' ./pkg/orchestration -v
# A locked git commit only the host's git configuration reaches is
# mirrored on the host, by id and through a host that refuses unadvertised
# commits, and an image build with no network fetches it from the mirror
# context and fails without it. Needs git and Docker; no provider toolchain.
- name: Prove a git source build needs no credential with its prefetched mirror
if: matrix.gate == 'control-integration'
run: go test -tags=integration -run '^(TestALockedGitSourceIsMirroredWithTheHostsGitConfiguration|TestAGitSourceBuildNeedsNoCredentialWithThePrefetchedMirror)$' ./pkg/orchestration -v
# Keep the tag in step with qualificationImage in
# cmd/generate/container_recovery_docker_test.go.
- name: Pull the interrupted-generate proof's image
if: matrix.gate == 'control-integration'
run: docker pull mirror.gcr.io/library/alpine:3.22
# `codefly generate` builds its containers in the CLI process rather than
# through an agent, so no fleet rebuild can make them recoverable and no
# unit test can prove they are: the labels and the sweep that reads them
# only exist against a real daemon. This is the only container-recovery
# dimension the CLI can qualify without a rebuilt, published agent —
# cli#658 carries the rest.
- name: Run the interrupted-generate container-recovery proof
if: matrix.gate == 'control-integration'
run: go test -tags=integration -run '^TestAnInterruptedGenerateLeavesARecoverableContainer$' ./cmd/generate -v
# `generate proto` stages generation and publishes from the staged tree,
# so what a run emitted is known independently of what its outputs
# already held (#885). That rests on the companion and the host sharing
# one staging directory, and on what it emits there being the invoking
# user's to publish and remove — buf's output directories are 0700, and
# only a real Linux bind mount preserves the creator's identity, which
# desktop Docker's ownership mapping hides (#895). Neither is provable
# against a fake runner. Shares the alpine image pulled above, so the
# host boundary is proven without pulling a provider toolchain.
- name: Qualify the generate-proto staging mount against a real bind mount
if: matrix.gate == 'control-integration'
run: go test -tags=integration -run '^TestProtoStagingMountIsQualifiedAgainstARealBindMount$' ./cmd/generate -v
- name: Verify source attachment across the generic protocol
if: matrix.gate == 'control-integration'
run: go test -tags=integration -run '^TestSourceAttachmentPreservesSelectionAndBuilderIdentity$' ./pkg/sourceworkspace -v
# Always-run so a failed or filtered-out test still has to account for
# the rows its job claimed. Keep these last: they audit every step above.
- name: Verify conformance receipts
if: always() && matrix.conformance_rows != ''
run: ./scripts/verify-conformance-receipts.sh
- name: Upload conformance receipts
if: always() && matrix.conformance_rows != ''
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: conformance-receipts-${{ matrix.gate }}
path: conformance-receipts/
if-no-files-found: warn
# golangci-lint runs as its own job, not a quality gate, for two reasons:
# a new-issues baseline has a well-defined meaning only against a
# pull-request base branch (on push there is no diff to gate against), and a
# cold-cache whole-repository analysis needs more headroom than the 5-minute
# quality budget. Pinned to match the version documented in the Makefile
# (`make lint`). New issues gate the PR so the pre-existing backlog does not
# block work while the standard is enforced.
#
# No baseline: the whole repository is linted, and the rule is that the
# full run is clean at the head — pre-existing findings are fixed in the
# PR that meets them, never hidden behind "new issues only". (The action's
# only-new-issues stays off for a second reason: it fetches the PR's patch
# through a GitHub API that refuses diffs past 20,000 lines and then
# reports every issue as new.) `make lint` runs the same gate locally.
lint:
name: lint
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
timeout-minutes: 10
steps:
- name: checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# The whole history, so the linter can compute the merge-base
# itself. The action's own only-new-issues mode asks GitHub for the
# pull request's patch, and GitHub refuses one over 20,000 lines
# ("Sorry, the diff exceeded the maximum number of lines") -- at
# which point the action has no baseline and FALLS BACK TO REPORTING
# THE ENTIRE BACKLOG, 753 findings on a branch that introduced none.
# A gate that answers about the backlog on a large PR and about the
# diff on a small one is not a gate. --new-from-merge-base below
# needs the base commit present locally, which fetch-depth: 1 does
# not provide.
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: go.mod
cache: false
- name: golangci-lint
uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9.3.0
with:
# v2.14.0 bundles x/tools v0.50.0, whose reader supports Go 1.27.2's
# export data version 5; v2.13.1 only supports version 4. Keep in sync with
# GOLANGCI_LINT_VERSION in the Makefile.
version: v2.14.0
# Build the linter from source under the module's Go toolchain
# (1.27) instead of downloading the prebuilt binary, which is built
# with an older Go and refuses to lint a module targeting a newer
# one ("the Go language version used to build golangci-lint is lower
# than the targeted Go version"). Matches `make lint`.
install-mode: goinstall
only-new-issues: false
# pkg/web/go-grpc/out is a committed, generated artifact: web/dashboard builds
# into it and go:embed ships it inside the CLI binary (see the Makefile's
# `dashboard` target, which is the only other place it is produced).
#
# Nothing else in CI runs npm, so without this job a dependency bump under
# web/dashboard changes package.json and package-lock.json, leaves out/
# untouched, and passes every Go gate — TestHandlerServesDashboard included,
# because that test asserts against the committed bundle and so cannot notice
# that the bundle no longer corresponds to the declared dependencies. The
# binary would ship a dashboard built from the old dependency tree while the
# lockfile claimed the new one, and the drift would surface at the next manual
# `make dashboard`, several merges downstream of the bump that caused it.
#
# The build is byte-reproducible from the lockfile, so a clean tree is a fair
# gate rather than a flaky one.
dashboard:
name: dashboard
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
# Pinned rather than left to the runner default: this job's whole
# value is that a dirty out/ means the dependencies moved, so the
# toolchain producing out/ must not drift underneath it.
node-version: '24'
cache: npm
cache-dependency-path: web/dashboard/package-lock.json
# npm ci, not npm install: it fails closed on a package.json that has
# drifted from the lockfile instead of silently resolving something else.
- name: Rebuild the embedded dashboard
run: |
set -euo pipefail
cd web/dashboard
npm ci
npm run build
# --intent-to-add so a newly emitted asset (an untracked file) is visible
# to `git diff`; without it the check would only catch modifications and
# deletions, and a build that added a chunk would pass while out/ was
# missing it.
- name: Fail if the committed bundle is stale
run: |
set -euo pipefail
git add --intent-to-add -- pkg/web/go-grpc/out
if ! git diff --exit-code -- pkg/web/go-grpc/out; then
echo "::error::pkg/web/go-grpc/out does not match a build of web/dashboard. Run 'make dashboard' and commit the result."
exit 1
fi