Repository navigation
release: v0.1.225 #3271
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |