Repository navigation
358 lines (357 loc) · 17.4 KB
/
Copy pathpr-workflow.yaml
File metadata and controls
358 lines (357 loc) · 17.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
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
name: pr-workflow
on:
pull_request:
merge_group:
push:
branches: [main]
schedule:
# Weekly run on the default branch keeps the micro-VM asset cache warm (GitHub
# evicts caches idle for 7 days). The push-to-main run populates the cache that
# PRs inherit; this refreshes it so PRs keep hitting it (no asset re-download).
- cron: '37 4 * * 1'
# Nothing here writes to the repository: the jobs check the tree out, build it,
# and run it in a throwaway cluster.
permissions:
contents: read
# Superseded pushes to a pull request are cancelled; every commit that lands on
# main still gets its own full run.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
run-tests:
runs-on: ubuntu-latest
# A bound well clear of the observed runtime. Without one the platform
# default applies, and a wedged step burns a free-tier slot for six hours.
timeout-minutes: 45
env:
# Where each runner writes its JUnit. Every invocation needs its own file;
# a shared path means the last writer wins.
ARTIFACTS: ${{ github.workspace }}/_artifacts
steps:
- name: Checkout
uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
with:
go-version-file: 'go.mod'
# Unconditional: a released migration becoming permanent is exactly what
# this guards, and that happens on the push to main, not on the PR.
- name: Verify immutable PostgreSQL migrations
run: hack/verify/postgresql-migrations.sh
# we only test on amd64, make sure everything still builds for arm64
- name: build for arm64
run: GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build ./...
# gotestsum wraps go test to emit JUnit and the go test -json stream.
# the log only gets a line per package and failed test output, the full
# output is in the uploaded json
# NOTE: no -v with gotestsum, it replaces the -test.v=test2json that -json
# sets, which marks t.Error output for the failure report below
- name: unit tests
run: |
go -C tools/junittool run . register "${ARTIFACTS}/unit.xml"
hack/run-tool.sh gotestsum --junitfile "${ARTIFACTS}/unit.xml" \
--jsonfile "${ARTIFACTS}/unit.json" \
--format pkgname-and-test-fails --format-hide-empty-pkg \
-- -race ./...
# tools/apitool has its own module so we need to run it explicitly.
# run-tool.sh resolves the repo root itself, so it works from the subdir.
- name: apitool tests
working-directory: tools/apitool
run: |
go -C ../junittool run . register "${ARTIFACTS}/apitool.xml"
../../hack/run-tool.sh gotestsum --junitfile "${ARTIFACTS}/apitool.xml" \
--jsonfile "${ARTIFACTS}/apitool.json" \
--format pkgname-and-test-fails --format-hide-empty-pkg \
-- -race ./...
# tools/junittool has its own module too
- name: junittool tests
working-directory: tools/junittool
run: |
go run . register "${ARTIFACTS}/junittool.xml"
../../hack/run-tool.sh gotestsum --junitfile "${ARTIFACTS}/junittool.xml" \
--jsonfile "${ARTIFACTS}/junittool.json" \
--format pkgname-and-test-fails --format-hide-empty-pkg \
-- -race ./...
# Kept apart from the root module so it can move to its own repository.
- name: gcp-secret-manager credential provider tests
working-directory: internal/plugins/gcp-secret-manager
run: |
go -C ../../../tools/junittool run . register "${ARTIFACTS}/gcp-secret-manager.xml"
../../../hack/run-tool.sh gotestsum --junitfile "${ARTIFACTS}/gcp-secret-manager.xml" \
--jsonfile "${ARTIFACTS}/gcp-secret-manager.json" \
--format pkgname-and-test-fails --format-hide-empty-pkg \
-- -race ./...
# The root verifiers stop at the module's go.mod, so it gets its own run:
# gofmt, go vet and the tidy check, then golangci-lint with the root config.
- name: gcp-secret-manager credential provider verify
working-directory: internal/plugins/gcp-secret-manager
run: |
make verify
../../../hack/run-tool.sh golangci-lint run --config ../../../.golangci.yaml ./...
- name: Cache cargo
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: |
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git/db
cmd/dataplane/envoy/dynamic-modules/target
key: cargo-${{ runner.os }}-${{ hashFiles('cmd/dataplane/envoy/dynamic-modules/Cargo.lock') }}
restore-keys: cargo-${{ runner.os }}-
- name: dynamic module tests
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq --no-install-recommends clang libclang-dev
hack/test-dynamic-modules.sh
# Root-gated tests (overlay mounts, whiteout mknod, trusted.* xattrs, ...)
# skip for the unprivileged runner user above; rerun the packages that
# contain them (any test importing internal/roottest) under sudo.
- name: root-gated tests
env:
ROOT_JUNIT_FILE: ${{ github.workspace }}/_artifacts/root.xml
run: hack/run-root-tests.sh -race
# An exit code cannot distinguish "all passed" from "none ran". A registered
# file that was never written fails too.
- name: Require tests to have run
if: always()
run: go -C tools/junittool run . verify -manifest "${ARTIFACTS}/expected-junit.txt"
# list each failed test and its error in the job summary and as annotations
- name: Summarize test failures
if: always()
run: go -C tools/junittool run . report "${ARTIFACTS}"
- name: Upload test results
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: test-results-unit
path: ${{ env.ARTIFACTS }}
if-no-files-found: error
- name: verify
run: hack/verify-all.sh
# One kind cluster exercises BOTH runtimes. Free x86-64 ubuntu-latest runners
# expose /dev/kvm (with a udev rule), so create-kind-cluster.sh mounts it and the
# micro-VM (kata + cloud-hypervisor) sandbox class works alongside gVisor.
#
# Each matrix entry is an independent job on a fresh cluster, so the steps
# below are written once and run for every dataplane and preview mode. The
# envoy entry with preview features off reports the required e2e-test check.
e2e-test:
name: >-
${{ (matrix.dataplane == 'envoy' && matrix.preview == 'off') && 'e2e-test'
|| format('e2e-test ({0}{1})', matrix.dataplane, matrix.preview == 'on' && ', preview' || '') }}
runs-on: ubuntu-latest
# Cluster bring-up, image builds and two sequential lanes; generous, but far
# short of the six-hour platform default a hung lane would otherwise consume.
timeout-minutes: 120
continue-on-error: ${{ matrix.dataplane == 'agentgateway' }} # TODO: Make AgentGateway required once tests show stability
strategy:
fail-fast: false
matrix:
# AgentGateway runs pre-submit only for release-* branches. Everywhere
# else it runs post-merge (push to main) and on the weekly schedule.
dataplane: >-
${{ fromJSON(
(
(github.event_name != 'pull_request' && github.event_name != 'merge_group')
|| startsWith(github.base_ref, 'release-')
|| startsWith(github.event.merge_group.base_ref, 'refs/heads/release-')
)
&& '["envoy", "agentgateway"]'
|| '["envoy"]'
) }}
# "off" is the default install, and proves the suites pass without
# preview features; "on" enables every one of them.
preview: ['off', 'on']
env:
E2E_ATENET_DATAPLANE: ${{ matrix.dataplane }}
# The installer requires a credential provider selection, and deploys the
# bundled Kubernetes Secrets provider with the control plane. Job-wide
# because every ate-setup invocation reads it, including the control
# plane redeploy in run-microvm-demo-kind.sh.
ATE_CREDENTIAL_PROVIDER: '{"name":"k8s.io"}'
# Job-wide for the same reason. Empty is an explicit "no preview
# features", not an unset value. The e2e steps pass it to the suites as
# --preview, so tests of a disabled gate skip themselves (see
# internal/e2e/preview.go).
ATE_PREVIEW: ${{ matrix.preview == 'on' && 'ALL' || '' }}
# Appended to the lane and artifact names: e2e-gvisor runs without
# preview features, e2e-gvisor-preview with them.
LANE_SUFFIX: ${{ matrix.preview == 'on' && '-preview' || '' }}
# E2E_JUNIT_FILE is set per step, not here: the lanes below share this
# job, so one job-level path would have each overwrite the last.
ARTIFACTS: ${{ github.workspace }}/_artifacts
# Cache the envoy-dataplane Rust build. Only main writes it, so PRs
# share one copy instead of each storing their own.
DOCKER_BUILD_FLAGS: >-
--cache-from type=gha,scope=envoy-dataplane
${{ github.ref == 'refs/heads/main' && matrix.dataplane == 'envoy' && matrix.preview == 'off'
&& '--cache-to type=gha,mode=max,scope=envoy-dataplane,ignore-error=true' || '' }}
steps:
- name: Checkout
uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
- name: Setup Go
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
with:
go-version-file: 'go.mod'
# Use the e2e cache below instead. setup-go's cache key is shared with
# run-tests, whose unit-test builds don't match ko's flags
# (CGO_ENABLED=0, -trimpath), so ko got no cache hits from it.
cache: false
- name: Restore Go build cache
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: e2e-go-${{ runner.os }}-${{ github.sha }}
restore-keys: e2e-go-${{ runner.os }}-
- name: Set up Docker Buildx
# The docker-container driver is what can use the GitHub Actions cache.
# Host networking and plain HTTP let it push to the kind registry.
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
with:
driver-opts: network=host
buildkitd-config-inline: |
[registry."localhost:5001"]
http = true
- name: Expose GitHub runtime to buildx
# buildx's gha cache backend reads these from the environment, but the
# runner only passes them to actions, not to run steps.
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
for (const [key, value] of Object.entries(process.env)) {
if (key.startsWith('ACTIONS_')) core.exportVariable(key, value);
}
- name: Cache micro-VM assets
id: microvm-assets
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
# Assembling the assets (download kata-static + cloud-hypervisor + the
# prebuilt virtiofsd — all downloads on amd64) is fully pinned by assemble.sh,
# so key the cache on its hash. The push-to-main run populates the
# default-branch cache PRs inherit; the weekly schedule refreshes it.
# run-microvm-demo-kind.sh skips assembling when these are present.
path: bin/microvm-assets/amd64
key: microvm-assets-amd64-${{ hashFiles('hack/microvm-assets/assemble.sh') }}
- name: Enable KVM
# Grant the runner access to /dev/kvm so create-kind-cluster.sh mounts it into
# the node and labels it for the micro-VM sandbox class.
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \
| sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Create cluster
run: hack/create-kind-cluster.sh
- name: Install Agent Substrate (${{ matrix.dataplane }})
# The dataplane selection applies to both the ingress router and egress
# gateway. ATE_PREVIEW above selects the preview features.
env:
ATE_ATENET_DATAPLANE: ${{ matrix.dataplane }}
run: hack/install-ate-kind.sh --deploy-ate-system
- name: Enable NFS
# Load NFS kernel modules so in-cluster NFS server and CSI driver can run.
run: |
sudo modprobe nfs || true
sudo modprobe nfsd || true
- name: Install CSI NFS driver
run: hack/install-ate-kind.sh --setup-csi=nfs
- name: Deploy micro-VM counter demo
# Stages the (cached) assets into the cluster's rustfs and deploys the
# counter-microvm demo onto the control plane installed above. The demo
# redeploys the control plane, so retain the selected dataplane.
env:
ATE_ATENET_DATAPLANE: ${{ matrix.dataplane }}
run: hack/run-microvm-demo-kind.sh
- name: Deploy gVisor counter demo
run: hack/install-ate-kind.sh --deploy-demo-counter
- name: Deploy egress demos
# TestActorEgress in the networking suite builds its Actor from the egress
# ActorTemplate for the class under test, so both fixtures have to exist
# before their respective lane runs. The demo's actors trust the CA the
# gateway mints its leaves from.
run: |
hack/install-ate-kind.sh --deploy-demo-egress
hack/install-ate-kind.sh --deploy-demo-egress-microvm
- name: Trust local TLS origins in egress gateway
run: hack/setup-e2e-egress-tls-kind.sh
# scale the idle sandbox class's demo pools to zero so each lane has the node
- name: Focus the node on gVisor
run: hack/e2e-focus-sandbox-class.sh gvisor
- name: Run E2E tests (gVisor)
env:
E2E_PARALLELISM: 8
E2E_JUNIT_FILE: ${{ github.workspace }}/_artifacts/e2e-gvisor${{ env.LANE_SUFFIX }}.xml
run: hack/run-e2e-kind.sh -args --no-color --preview="${ATE_PREVIEW}"
- name: Focus the node on micro-VM
run: hack/e2e-focus-sandbox-class.sh microvm
- name: Run E2E tests (micro-VM)
# The same suites again, with every fixture repointed at its micro-VM
# variant by the single E2E_SANDBOX_CLASS knob (see internal/e2e/sandbox.go).
env:
E2E_SANDBOX_CLASS: microvm
E2E_PARALLELISM: 8
E2E_JUNIT_FILE: ${{ github.workspace }}/_artifacts/e2e-microvm${{ env.LANE_SUFFIX }}.xml
run: hack/run-e2e-kind.sh -args --no-color --preview="${ATE_PREVIEW}"
# Every lane must have reported tests; a `-run` filter that stops matching
# otherwise leaves its lane green and empty. The set comes from the manifest
# each lane registers itself in, so adding a lane needs no change here, and
# run-e2e.sh refuses to run in CI without E2E_JUNIT_FILE.
- name: Require every lane to have run tests
if: always()
run: go -C tools/junittool run . verify -manifest "${ARTIFACTS}/expected-junit.txt"
- name: Summarize test failures
if: always()
run: go -C tools/junittool run . report "${ARTIFACTS}"
- name: Save Go build cache
# Main only, so PRs restore the latest main build rather than each
# storing its own copy.
if: ${{ !cancelled() && github.ref == 'refs/heads/main' && matrix.dataplane == 'envoy' && matrix.preview == 'off' }}
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: e2e-go-${{ runner.os }}-${{ github.sha }}
- name: Upload test results
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: test-results-e2e-${{ matrix.dataplane }}${{ env.LANE_SUFFIX }}
path: ${{ env.ARTIFACTS }}
if-no-files-found: error
- name: Dump diagnostics on failure
if: failure()
run: |
kubectl --context kind-kind get workerpool,pods -A -o wide || true
dump() {
echo "=== logs: $1/$2 ==="
kubectl --context kind-kind logs -n "$1" "$2" --all-containers --tail=300 2>/dev/null || true
}
for p in $(kubectl --context kind-kind get pods -n ate-system -o name 2>/dev/null); do
dump ate-system "$p"
done
# Every worker pod in any namespace: the demo pools plus the e2e suites'
# randomly-named per-test namespaces, which the suites keep on failure.
# The failing actor runs in one of these, so this is where its worker logs
# (and, for a micro-VM worker, the guest console tail) live.
kubectl --context kind-kind get pods -A -l ate.dev/worker-pool \
-o 'custom-columns=:.metadata.namespace,:.metadata.name' --no-headers 2>/dev/null \
| while read -r ns name; do dump "$ns" "$name"; done