-
Notifications
You must be signed in to change notification settings - Fork 14
571 lines (553 loc) · 23.4 KB
/
Copy pathci.yml
File metadata and controls
571 lines (553 loc) · 23.4 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
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
name: CI
# Public repo → GitHub-hosted Actions are free. Kept fast: Build and Vet share
# one job so `go vet` reuses the compile cache `go build` just populated
# (standalone `go vet ./...` recompiles everything and is slow). Test is sharded
# across a matrix and runs in parallel; the quick checks (Tidy/Format) give
# sub-minute feedback. Every job restores the Go build+module cache. One run per
# PR (+ push to master); superseded runs cancel.
#
# Pipeline-time controls (see #837):
# * Test is split into a 4-way package-sharded matrix; a "Test" gate job
# aggregates the shards so the required-check name is preserved.
# * Race runs a fast subset on PRs and the full `-race ./...` tree nightly on a
# schedule (advisory — a mid-day race is caught by the nightly run).
# * A docs-only PR (only `*.md` / `docs/**`) skips the heavy Go jobs (Test,
# Race, Contrib, Compat). Those are skipped at the JOB level via `if:`, which
# GitHub reports as a successful check — so required checks are not left
# pending and docs PRs stay mergeable. The skip is fail-safe: the heavy jobs
# run unless the classifier explicitly said docs-only (`code=='false'`), so a
# classifier failure runs the full suite rather than skipping it, and the
# "Test" gate is fail-closed (it only counts a skip as a pass on a genuine
# docs-only classifier success).
on:
pull_request:
push:
branches: [master]
schedule:
# Nightly full-tree race detector (03:00 UTC). On PRs Race runs a fast
# subset only; the exhaustive `-race ./...` sweep runs here.
- cron: "0 3 * * *"
permissions:
contents: read
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
# Classifies the change so the heavy Go jobs can be skipped for a docs-only
# PR. `code=true` unless EVERY changed file is `*.md` or under `docs/**`
# (anything else — .go, go.mod, fixtures, workflows — forces the full run, so
# a non-docs change can never silently skip the tests). push/schedule always
# run the full pipeline.
changes:
name: Detect changes
runs-on: ubuntu-latest
outputs:
code: ${{ steps.classify.outputs.code }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- id: classify
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail
if [ "${{ github.event_name }}" != "pull_request" ]; then
echo "non-PR event (${{ github.event_name }}) → run full pipeline"
echo "code=true" >> "$GITHUB_OUTPUT"
exit 0
fi
files="$(git diff --name-only "$BASE_SHA...$HEAD_SHA")"
echo "changed files:"
echo "$files"
code=false
while IFS= read -r f; do
[ -z "$f" ] && continue
case "$f" in
*.md) ;; # markdown anywhere → docs
docs/*) ;; # anything under docs/ → docs ('*' matches '/')
*) code=true ;;
esac
done <<EOF
$files
EOF
echo "code=$code"
echo "code=$code" >> "$GITHUB_OUTPUT"
build-vet:
name: Build & Vet
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.25"
cache: false
# Manual cache with restore-keys so a changed go.sum still restores the
# previous build+module cache (only the changed deps recompile) instead
# of a full cold compile of the whole cloud-SDK dependency tree.
- uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: go-${{ runner.os }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
go-${{ runner.os }}-
- name: go build
run: go build ./...
- name: go vet (reuses the build cache)
run: go vet ./...
contrib-testcontainers:
name: Contrib (testcontainers)
runs-on: ubuntu-latest
needs: changes
# Fail-safe: run unless the change was a genuine docs-only skip
# (code=='false'). If the classifier failed/errored, `code` is empty and the
# job RUNS (a classifier hiccup never silently skips a heavy job).
if: ${{ !cancelled() && needs.changes.outputs.code != 'false' }}
# Separate Go module (keeps testcontainers-go out of the core module), so
# the root build/vet doesn't cover it. Compile + vet only — the container
# acceptance test needs Docker and is run on demand (go test ./...).
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.25"
cache: false
- uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: go-${{ runner.os }}-contrib-${{ hashFiles('contrib/testcontainers/go.sum') }}
restore-keys: |
go-${{ runner.os }}-contrib-
- name: build & vet
working-directory: contrib/testcontainers
run: |
go build ./...
go vet ./...
contrib-realengine:
name: Contrib (realengine)
runs-on: ubuntu-latest
needs: changes
# Fail-safe: run unless the change was a genuine docs-only skip
# (code=='false'). If the classifier failed/errored, `code` is empty and the
# job RUNS (a classifier hiccup never silently skips a heavy job).
if: ${{ !cancelled() && needs.changes.outputs.code != 'false' }}
# Separate Go module (keeps embedded-postgres out of the core module). Build
# + vet only — the real-Postgres tests fetch a Postgres binary and run it,
# so they are executed on demand (go test ./...), not on every CI run.
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.25"
cache: false
- uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: go-${{ runner.os }}-realengine-${{ hashFiles('contrib/realengine/go.sum') }}
restore-keys: |
go-${{ runner.os }}-realengine-
- name: build & vet
working-directory: contrib/realengine
run: |
go build ./...
go vet ./...
contrib-dockerengine:
name: Contrib (dockerengine)
runs-on: ubuntu-latest
needs: changes
# Fail-safe: run unless the change was a genuine docs-only skip
# (code=='false'). If the classifier failed/errored, `code` is empty and the
# job RUNS (a classifier hiccup never silently skips a heavy job).
if: ${{ !cancelled() && needs.changes.outputs.code != 'false' }}
# Separate Go module (keeps the docker-CLI-oriented deps out of the core
# module). Build + vet + lint here; the real-container e2es (MySQL, compute,
# containers, the Azure Functions host) shell out to the docker CLI and pull
# multi-hundred-MB images, so — like realengine — they run on demand
# (go test ./...), not on every CI run. ubuntu-latest ships Docker for that.
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.25"
cache: false
- uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: go-${{ runner.os }}-dockerengine-${{ hashFiles('contrib/dockerengine/go.sum') }}
restore-keys: |
go-${{ runner.os }}-dockerengine-
- name: build & vet
working-directory: contrib/dockerengine
run: |
go build ./...
go vet ./...
- name: install golangci-lint v2
run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b "$(go env GOPATH)/bin"
- name: golangci-lint run (report-only)
working-directory: contrib/dockerengine
run: "$(go env GOPATH)/bin/golangci-lint run --timeout=9m ./... || echo '::warning::golangci-lint reported findings (advisory)'"
contrib-server:
name: Contrib (server)
runs-on: ubuntu-latest
needs: changes
# Fail-safe: run unless the change was a genuine docs-only skip
# (code=='false'). If the classifier failed/errored, `code` is empty and the
# job RUNS (a classifier hiccup never silently skips a heavy job).
if: ${{ !cancelled() && needs.changes.outputs.code != 'false' }}
# Separate Go module: the batteries-included wire server that composes the
# real engines onto the wire servers. Build + vet + lint here; the real-engine
# e2e (embedded Postgres + miniredis + real AWS SDK) fetches a Postgres binary
# and runs it, so — like realengine/dockerengine — it runs on demand
# (go test ./...), not on every CI run.
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.25"
cache: false
- uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: go-${{ runner.os }}-server-${{ hashFiles('contrib/server/go.sum') }}
restore-keys: |
go-${{ runner.os }}-server-
# Hard failure — keep the contrib module's compile check blocking (a
# go.sum/compile break here must fail CI, not merge silently).
- name: build & vet
working-directory: contrib/server
run: |
go build ./...
go vet ./...
# embedded-postgres downloads its Postgres binary on first run; cache it so
# the CI-safe test tier (no docker socket needed — the E2E uses embedded
# Postgres, the degrade tests run WITHOUT docker) doesn't refetch each run.
- uses: actions/cache@v4
with:
path: ~/.embedded-postgres-go
key: embedded-postgres-${{ runner.os }}
# Advisory (report-only), matching the contrib-* convention: these tests
# need no docker socket but the embedded-Postgres binary download can flake;
# promote to blocking once proven stable.
- name: go test (report-only)
working-directory: contrib/server
run: "go test -count=1 ./... || echo '::warning::contrib/server go test reported findings (advisory)'"
- name: install golangci-lint v2
run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b "$(go env GOPATH)/bin"
- name: golangci-lint run (report-only)
working-directory: contrib/server
run: "$(go env GOPATH)/bin/golangci-lint run --timeout=9m ./... || echo '::warning::golangci-lint reported findings (advisory)'"
# Only run the heavy multi-stage engines image build when the trees it is
# built from change, so it doesn't fire on every repo PR.
- name: filter engines-image paths
id: engines_paths
uses: dorny/paths-filter@v3
with:
filters: |
engines:
- 'contrib/server/**'
- 'contrib/realengine/**'
- 'contrib/dockerengine/**'
- 'server/serverkit/**'
- name: docker smoke build (engines image)
if: steps.engines_paths.outputs.engines == 'true'
run: docker build -f contrib/server/Dockerfile -t cloudemu-engines-test .
contrib-terraform:
name: Contrib (terraform)
runs-on: ubuntu-latest
needs: changes
# Fail-safe: run unless the change was a genuine docs-only skip
# (code=='false'). If the classifier failed/errored, `code` is empty and the
# job RUNS (a classifier hiccup never silently skips a heavy job).
if: ${{ !cancelled() && needs.changes.outputs.code != 'false' }}
# Separate Go module: the IaC-compatibility suite. Unlike the other contrib
# e2es (which need Docker), this one needs only an OpenTofu binary and a
# provider download, so — as the evidenced-IaC-compat proof — it actually
# runs the real `tofu init/apply/plan/destroy` loop in CI, not just build+vet.
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.25"
cache: false
- uses: opentofu/setup-opentofu@v1
with:
tofu_wrapper: false
- uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: go-${{ runner.os }}-terraform-${{ hashFiles('contrib/terraform/go.sum') }}
restore-keys: |
go-${{ runner.os }}-terraform-
- name: build & vet
working-directory: contrib/terraform
run: |
go build ./...
go vet ./...
- name: terraform compatibility e2e
working-directory: contrib/terraform
run: go test -timeout 15m ./...
- name: install golangci-lint v2
run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b "$(go env GOPATH)/bin"
- name: golangci-lint run (report-only)
working-directory: contrib/terraform
run: "$(go env GOPATH)/bin/golangci-lint run --timeout=9m ./... || echo '::warning::golangci-lint reported findings (advisory)'"
test:
name: Test (${{ matrix.shard }}/4)
runs-on: ubuntu-latest
needs: changes
# Fail-safe: run unless the change was a genuine docs-only skip
# (code=='false'). If the classifier failed/errored, `code` is empty and the
# job RUNS (a classifier hiccup never silently skips a heavy job).
if: ${{ !cancelled() && needs.changes.outputs.code != 'false' }}
strategy:
fail-fast: false
matrix:
# 4 package shards. `total` in the run step MUST equal this list's
# length. Shards partition `go list ./...` by line number modulo total,
# so every package lands in exactly one shard and the union is the whole
# tree (proof: residues {1,2,3,0} = all classes mod 4).
shard: [1, 2, 3, 4]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.25"
cache: false
- uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: go-${{ runner.os }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
go-${{ runner.os }}-
- name: go test (shard ${{ matrix.shard }}/4)
run: |
set -euo pipefail
shard=${{ matrix.shard }}
total=4
pkgs="$(go list ./... | awk -v s="$shard" -v t="$total" 'NR % t == s % t')"
if [ -z "$pkgs" ]; then
echo "shard $shard/$total is empty — nothing to test"
exit 0
fi
echo "shard $shard/$total: $(echo "$pkgs" | wc -l | tr -d ' ') packages"
# shellcheck disable=SC2086 # word-splitting the newline package list is intended
go test $pkgs
# Aggregating gate: keeps the required-check name "Test" stable after
# sharding. Branch protection should keep requiring "Test" (NOT the per-shard
# "Test (n/4)" names). This gate is fail-CLOSED — a skipped matrix only passes
# when it is a *genuine* docs-only skip (classifier succeeded AND said
# code=='false'). If the `changes` classifier itself failed/was cancelled, or
# the shards were skipped while code=='true', the gate goes RED — so a
# classifier hiccup can never green "Test" having run zero tests.
test-gate:
name: Test
runs-on: ubuntu-latest
needs: [changes, test]
if: always()
steps:
- name: shards result
run: |
changes_result="${{ needs.changes.result }}"
changes_code="${{ needs.changes.outputs.code }}"
test_result="${{ needs.test.result }}"
echo "changes.result=$changes_result changes.code=$changes_code test.result=$test_result"
if [ "$test_result" = "success" ]; then
echo "all Test shards passed"
exit 0
fi
if [ "$test_result" = "skipped" ] && [ "$changes_result" = "success" ] && [ "$changes_code" = "false" ]; then
echo "docs-only PR — Test shards legitimately skipped"
exit 0
fi
echo "Test gate FAILED: shards did not pass (shard failure, classifier failure, or an unexpected skip)"
exit 1
race:
name: Race
runs-on: ubuntu-latest
needs: changes
# Fail-safe: run unless the change was a genuine docs-only skip
# (code=='false'). If the classifier failed/errored, `code` is empty and the
# job RUNS (a classifier hiccup never silently skips a heavy job).
if: ${{ !cancelled() && needs.changes.outputs.code != 'false' }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.25"
cache: false
- uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: go-${{ runner.os }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
go-${{ runner.os }}-
# On PRs: a fast race signal over the concurrency-heavy Bedrock packages
# (guardrail versions, evaluation-job stop, etc.) — the same subset that
# previously rode along in the Test job. Nightly (schedule): the full
# whole-tree race detector. The per-entity-mutex idiom (see SQS
# queueData.mu / EC2 instanceData.mu; use memstore.Update, never Get+Set)
# keeps the tree race-clean; -race is 2-10x slower, hence the timeout.
- name: race (fast subset on PRs, full tree nightly)
run: |
set -euo pipefail
if [ "${{ github.event_name }}" = "schedule" ]; then
echo "nightly: full-tree race detector"
go test -race -timeout 20m ./...
else
echo "PR: fast race subset (Bedrock)"
go test -race ./providers/aws/bedrock/... ./server/aws/bedrock/... ./providers/aws/bedrockagent/... ./server/aws/bedrockagent/... ./server/aws/bedrockagentruntime/...
fi
tidy:
name: Tidy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.25"
cache: true
- name: go mod tidy is clean
run: |
go mod tidy
git diff --exit-code go.mod go.sum
compat:
name: Compat matrix
runs-on: ubuntu-latest
needs: changes
# Fail-safe: run unless the change was a genuine docs-only skip
# (code=='false'). If the classifier failed/errored, `code` is empty and the
# job RUNS (a classifier hiccup never silently skips a heavy job).
if: ${{ !cancelled() && needs.changes.outputs.code != 'false' }}
# Runs the in-process SDK-compat suite and regenerates docs/compat/ from the
# results + docs/coverage/coverage.json, then drift-checks it (same pattern
# as Tidy). A red cell fails the suite; a stale matrix fails the diff.
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.25"
cache: false
- uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: go-${{ runner.os }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
go-${{ runner.os }}-
- name: regenerate compat matrix
run: go run ./internal/compatgen
- name: compat matrix is up to date
run: git diff --exit-code docs/compat/
structure:
name: Structure
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: naming & layout convention (docs/STRUCTURE.md)
run: |
# No cloud-prefixed directory at ANY depth under providers/<cloud> or
# server/<cloud> — the parent aws/azure/gcp already encodes the cloud,
# so the prefix is redundant (docs/STRUCTURE.md §2.2). Recursive so a
# promoted sub-surface subdir (§4/§5) can't smuggle a prefix back in.
fail=0
for cloud in aws azure gcp; do
for layer in providers server; do
[ -d "$layer/$cloud" ] || continue
for d in $(find "$layer/$cloud" -mindepth 1 -type d); do
case "$(basename "$d")" in
"$cloud"?*)
echo "structure: '$d' has a redundant '$cloud' prefix — drop it (docs/STRUCTURE.md §2.2)"
fail=1 ;;
esac
done
done
done
if [ "$fail" -ne 0 ]; then
echo "Structure check failed. See docs/STRUCTURE.md."
exit 1
fi
echo "structure: no cloud-prefixed provider/wire directories — OK"
- name: same filename across layers (docs/STRUCTURE.md §3, advisory)
run: |
# STRUCTURE.md §3: a feature uses the same filename across the
# provider and wire layers, so one filename finds both the mock and
# the wire handler. Checked loosely — anywhere under server/<cloud>/,
# not necessarily the same <service> subdir, since a sub-surface can
# legitimately live under a differently-named service on each side
# (e.g. providers/aws/vpc/traffic_mirror.go /
# server/aws/ec2/traffic_mirror.go). Exempts each service's own
# core-CRUD file plus clone.go/tags.go — the per-directory template
# (§4) documents these as provider-only. Advisory: real pre-existing
# drift (renamed files, provider-only helpers) means this warns
# rather than fails a build.
missing=0
for cloud in aws azure gcp; do
provdir="providers/$cloud"
srvdir="server/$cloud"
[ -d "$provdir" ] || continue
[ -d "$srvdir" ] || continue
for f in $(find "$provdir" -mindepth 2 -name '*.go' ! -name '*_test.go' ! -path '*/driver/*'); do
base=$(basename "$f")
svc=$(basename "$(dirname "$f")")
case "$base" in
"$svc.go"|clone.go|tags.go) continue ;;
esac
if ! find "$srvdir" -name "$base" ! -name '*_test.go' | grep -q .; then
echo "structure: $f has no same-named file anywhere under $srvdir/ (docs/STRUCTURE.md §3)"
missing=$((missing + 1))
fi
done
done
echo "structure: $missing provider file(s) with no same-named wire-layer file"
if [ "$missing" -gt 0 ]; then
echo "::warning::$missing provider file(s) lack a same-named file under server/<cloud>/ (docs/STRUCTURE.md §3, advisory)"
fi
format:
name: Format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.25"
cache: true
- name: gofmt (report-only)
run: |
unformatted="$(gofmt -l .)"
if [ -n "$unformatted" ]; then
echo "::warning::gofmt needed on the following files (advisory):"
echo "$unformatted"
else
echo "all files gofmt-clean"
fi
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.25"
cache: true
- name: install golangci-lint v2
run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b "$(go env GOPATH)/bin"
- name: golangci-lint run (report-only)
run: "$(go env GOPATH)/bin/golangci-lint run --timeout=9m ./... || echo '::warning::golangci-lint reported findings (advisory)'"