Repository navigation
357 lines (323 loc) · 18.1 KB
/
Copy pathgo.yml
File metadata and controls
357 lines (323 loc) · 18.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
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