Skip to content

Commit 22b93cf

Browse files
authored
Merge branch 'main' into fix/structured-image-descriptor-digest
2 parents 61bb270 + f7c64ef commit 22b93cf

124 files changed

Lines changed: 9061 additions & 784 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/CLAUDE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ NVIDIA AI Cluster Runtime (AICR) generates validated GPU-accelerated Kubernetes
2828
state config vs actual manifests
2929
```
3030

31-
**Tech Stack:** Go 1.26, Kubernetes 1.33+, golangci-lint, Ko for images (pinned versions in `.settings.yaml`)
31+
**Tech Stack:** Go 1.26, Kubernetes, golangci-lint, Ko for images (pinned versions in `.settings.yaml`)
3232

3333
## Commands
3434

@@ -116,7 +116,7 @@ workspace paths. Use local file paths only when explicitly requested.
116116
| `pkg/snapshotter` | System state snapshot orchestration | Yes |
117117
| `pkg/k8s/client` | Singleton Kubernetes client | Yes |
118118
| `pkg/k8s/pod` | Shared K8s Job/Pod utilities (wait, logs, ConfigMap URIs) | Yes |
119-
| `pkg/validator/helper` | Shared validator helpers (PodLifecycle, test context) | Yes |
119+
| `validators/helper` | Shared validator helpers (PodLifecycle, GPU/resource utilities) | Yes |
120120
| `pkg/defaults` | Centralized timeout and configuration constants | Yes |
121121

122122
**Critical Architecture Principle:**

.github/RENOVATE.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Self-hosted Renovate keeps the project's dependencies up to date across `go.mod`
99

1010
- Configuration: [`.github/renovate.json5`](renovate.json5)
1111
- Workflow: [`.github/workflows/renovate.yaml`](workflows/renovate.yaml)
12-
- Companion script: [`tools/update-chainsaw-checksums`](../tools/update-chainsaw-checksums)
12+
- Companion scripts: [`tools/update-chainsaw-checksums`](../tools/update-chainsaw-checksums), [`tools/update-helmfile-checksums`](../tools/update-helmfile-checksums), [`tools/update-helm-diff-checksums`](../tools/update-helm-diff-checksums)
1313

1414
Policy choices (schedule, cooldown, auto-merge scope, group consolidation) are documented inline in `renovate.json5`. This doc covers what's covered, how to extend coverage, and the known gotchas.
1515

@@ -25,6 +25,8 @@ Policy choices (schedule, cooldown, auto-merge scope, group consolidation) are d
2525
| `.settings.yaml` (28 tool entries) | custom regex manager (`# renovate:` annotations) |
2626
| `.settings.yaml` `nvkind` SHA | dedicated git-refs digest customManager (`# renovate-digest:`) |
2727
| `.settings.yaml` `chainsaw_checksums` | `postUpgradeTasks``tools/update-chainsaw-checksums` |
28+
| `.settings.yaml` `helmfile_checksums` | `postUpgradeTasks``tools/update-helmfile-checksums` |
29+
| `.settings.yaml` `helm_diff_checksums` | `postUpgradeTasks``tools/update-helm-diff-checksums` |
2830
| `.go-version` (Go toolchain) | dedicated `golang-version` customManager (`go-toolchain` group) |
2931

3032
The `go` directive in `go.mod` is intentionally not bumped — the Go toolchain version is owned by `.go-version`. Makefile (`GOTOOLCHAIN`), the `load-versions` composite action, `install-karpenter-kwok`, and validator Dockerfiles (`--build-arg GO_VERSION`) all read from that single file.

.github/actions/e2e/action.yml

Lines changed: 96 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -43,38 +43,98 @@ runs:
4343
- name: Install E2E testing tools
4444
uses: ./.github/actions/install-e2e-tools
4545

46-
- name: Create Kind cluster
47-
shell: bash
48-
run: make cluster-create
49-
50-
- name: Deploy aicrd to Kind
46+
- name: Create Kind cluster and deploy aicrd
5147
shell: bash
5248
env:
5349
GOFLAGS: -mod=vendor
5450
run: |
55-
# Build and push aicrd image (replaces Tilt custom_build)
51+
# If any `wait` below fails, `set -e` (GitHub's default shell flags)
52+
# exits this script immediately - any background job not yet reached
53+
# by its own `wait` would otherwise keep running. Most consequential
54+
# for pid_cluster specifically: the job's always()-run Cleanup step
55+
# (`make cluster-delete`) could then race a still-in-flight
56+
# `make cluster-create`. Kill whatever is still alive on any exit.
57+
cleanup_background_jobs() {
58+
local rc=$?
59+
for pid in "$pid_cluster" "$pid_dep" "$pid_perf" "$pid_conf" "$pid_cli" "$pid_aicr" "$pid_validators"; do
60+
[ -n "$pid" ] && kill -0 "$pid" 2>/dev/null && kill "$pid" 2>/dev/null
61+
done
62+
exit "$rc"
63+
}
64+
trap cleanup_background_jobs EXIT
65+
66+
# This step folds what used to be two separate steps (Create Kind
67+
# cluster / Deploy aicrd) into one, so GitHub's per-step red-X no
68+
# longer says at a glance which phase failed. Group markers fold the
69+
# log into the same phases instead.
70+
echo "::group::cluster-and-builds"
71+
# cluster-create (~35s locally; Kind bring-up + registry) and the
72+
# host compiles below touch none of each other's inputs/outputs, so
73+
# launch everything in the background up front. Each later command
74+
# waits only on the specific PID(s) its own inputs actually depend
75+
# on: aicrd's ko build needs just the cluster/registry (not the
76+
# validator/CLI binaries), so it must not be gated behind builds it
77+
# doesn't read from - an earlier version of this step wait'ed on all
78+
# four build PIDs before it, which measured ~19s slower in CI than
79+
# the original sequential script for no reason: nothing here reads
80+
# dist/validator/* or dist/e2e/aicr until the docker build step below.
81+
pid_cluster=""; pid_dep=""; pid_perf=""; pid_conf=""; pid_cli=""; pid_aicr=""; pid_validators=""
82+
make cluster-create > /tmp/cluster-create.log 2>&1 &
83+
pid_cluster=$!
84+
85+
mkdir -p dist/validator dist/e2e
86+
for phase in deployment performance conformance; do
87+
mkdir -p "validators/${phase}/testdata"
88+
done
89+
90+
# Compile validator binaries + the host aicr CLI on the runner (Go
91+
# build cache hit) while the cluster comes up. This is also much
92+
# faster than building inside Docker (no cache sharing there). Go's
93+
# build cache is safe for concurrent use by simultaneous go
94+
# commands, including ko's own internal compile of aicrd below.
95+
CGO_ENABLED=0 go build -trimpath -o dist/validator/deployment ./validators/deployment &
96+
pid_dep=$!
97+
CGO_ENABLED=0 go build -trimpath -o dist/validator/performance ./validators/performance &
98+
pid_perf=$!
99+
CGO_ENABLED=0 go build -trimpath -o dist/validator/conformance ./validators/conformance &
100+
pid_conf=$!
101+
go build -o dist/e2e/aicr ./cmd/aicr &
102+
pid_cli=$!
103+
104+
# aicrd only needs the cluster/registry - build and push it (replaces
105+
# Tilt custom_build) as soon as that's ready, not gated behind the
106+
# unrelated validator/CLI builds above.
107+
#
108+
# cluster-create's output was redirected to a file above so it
109+
# wouldn't interleave with the concurrent builds; print it on
110+
# failure too (not just success), since a failed `wait` here is the
111+
# most common, most opaque failure on this step and would otherwise
112+
# exit via the EXIT trap with zero diagnostic output.
113+
wait $pid_cluster || { cat /tmp/cluster-create.log; exit 1; }
114+
cat /tmp/cluster-create.log
115+
echo "::endgroup::"
116+
117+
echo "::group::deploy-aicrd"
56118
KO_DOCKER_REPO=localhost:5001/aicrd ko build --bare --tags=tilt ./cmd/aicrd
57119
58120
# Apply namespace first (must exist before deployment references it)
59121
kubectl apply -f tilt/k8s/namespace.yaml
60122
kubectl apply -f tilt/k8s/
61-
62-
# Build images + CLI binary in parallel while deployment starts
123+
echo "::endgroup::"
124+
125+
echo "::group::validator-images"
126+
# The validator images COPY the binaries built above, so wait for
127+
# those specifically (not pid_cli, which nothing here needs) before
128+
# building them.
129+
wait $pid_dep
130+
wait $pid_perf
131+
wait $pid_conf
132+
133+
# Build + push the remaining images. Validator binaries are already
134+
# compiled above; this is just the COPY-only image build + push.
63135
KO_DOCKER_REPO=localhost:5001/aicr ko build --bare --tags=local ./cmd/aicr &
64136
pid_aicr=$!
65-
# Compile validator binaries on host (Go build cache hit) then COPY-only images.
66-
# This is much faster than building inside Docker (no cache sharing).
67137
(
68-
mkdir -p dist/validator
69-
CGO_ENABLED=0 go build -trimpath -o dist/validator/deployment ./validators/deployment &
70-
CGO_ENABLED=0 go build -trimpath -o dist/validator/performance ./validators/performance &
71-
CGO_ENABLED=0 go build -trimpath -o dist/validator/conformance ./validators/conformance &
72-
wait
73-
# Build per-phase COPY-only images and push to local registry.
74-
# Ensure testdata dirs exist (conformance has none).
75-
for phase in deployment performance conformance; do
76-
mkdir -p "validators/${phase}/testdata"
77-
done
78138
for phase in deployment performance conformance; do
79139
docker build -t "localhost:5001/aicr-validators/${phase}:latest" -f - . <<DOCKERFILE
80140
FROM nvcr.io/nvidia/distroless/static:v4.0.0@sha256:d90158b69e250d2018f32622b5c622925202ee97224a990a54b63811cb1e3d69
@@ -99,10 +159,14 @@ runs:
99159
docker push "localhost:5001/aicr-validators/aiperf-bench:latest"
100160
) &
101161
pid_validators=$!
102-
go build -o dist/e2e/aicr ./cmd/aicr &
103-
pid_cli=$!
104-
wait $pid_aicr $pid_validators $pid_cli
105-
162+
# Separate waits, not `wait $pid_aicr $pid_validators`: bash returns
163+
# only the LAST pid's exit status from a combined wait, so a failed
164+
# pid_aicr would be silently masked whenever pid_validators succeeds.
165+
wait $pid_aicr
166+
wait $pid_validators
167+
echo "::endgroup::"
168+
169+
echo "::group::rollout-and-verify"
106170
# Wait for deployment rollout (may already be ready)
107171
kubectl rollout status deployment/aicrd -n aicr --timeout=120s
108172
@@ -112,6 +176,13 @@ runs:
112176
curl -sf http://localhost:5001/v2/aicr-validators/performance/tags/list
113177
curl -sf http://localhost:5001/v2/aicr-validators/conformance/tags/list
114178
curl -sf http://localhost:5001/v2/aicr-validators/aiperf-bench/tags/list
179+
echo "::endgroup::"
180+
181+
# Not needed by anything above, but its output (dist/e2e/aicr) is
182+
# used by a later step (AICR_BIN) - wait now so a build failure here
183+
# fails this step instead of surfacing as a confusing missing-binary
184+
# error two steps later, and so the step's own exit code reflects it.
185+
wait $pid_cli
115186
116187
- name: Set up fake GPU environment
117188
shell: bash
@@ -151,6 +222,7 @@ runs:
151222
shell: bash
152223
run: |
153224
mkdir -p /tmp/debug-artifacts
225+
cp /tmp/cluster-create.log /tmp/debug-artifacts/cluster-create.log 2>/dev/null || true
154226
kubectl get all --all-namespaces > /tmp/debug-artifacts/all-resources.txt || true
155227
kubectl get events --all-namespaces --sort-by='.lastTimestamp' > /tmp/debug-artifacts/events.txt || true
156228
kubectl logs -n aicr -l app.kubernetes.io/name=aicrd --tail=500 > /tmp/debug-artifacts/aicrd-logs.txt || true

.github/renovate.json5

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,16 @@
319319
},
320320
},
321321

322+
// ---- helm-diff bumps refresh per-arch SHA256 checksums via post-upgrade script ----
323+
{
324+
matchDepNames: ["databus23/helm-diff"],
325+
postUpgradeTasks: {
326+
commands: ["./tools/update-helm-diff-checksums {{{newVersion}}}"],
327+
fileFilters: [".settings.yaml"],
328+
executionMode: "update",
329+
},
330+
},
331+
322332
// ---- aiperf-bench base image is capped below python 3.14 ----
323333
// PR #1906 auto-bumped this to 3.14 and broke the UAT validator image
324334
// build (aiperf's pyzmq/uvloop had no cp314 wheels, and the single-stage

.github/workflows/codeql.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@ jobs:
3939
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
4040
with:
4141
go-version: ${{ steps.versions.outputs.go }}
42-
cache: false # vendor/ provides deps; disable to save disk on constrained runners
43-
- uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
42+
cache: true
43+
- uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
4444
with:
4545
languages: go
4646
- run: go build ./...
4747
env:
4848
GOFLAGS: -mod=vendor
49-
- uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
49+
- uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8

.github/workflows/fern-docs-preview-build.yml

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,28 @@ jobs:
6868
if git show-ref --verify --quiet "refs/tags/${version}"; then
6969
mkdir -p "fern/versions/${version}-content"
7070
git archive "refs/tags/${version}" -- docs/ | tar -x --strip-components=1 -C "fern/versions/${version}-content"
71-
find "fern/versions/${version}-content" -name '*.md' -print0 | xargs -0 sed -i \
72-
-e 's/{/\\{/g' \
73-
-e 's/}/\\}/g' \
74-
-e 's/</\&lt;/g'
71+
# Escape {, }, < for MDX — but only outside fenced code blocks and inline code spans
72+
find "fern/versions/${version}-content" -name '*.md' -print0 | while IFS= read -r -d '' f; do
73+
awk '
74+
/^````*/ || /^~~~~*/ { fence = !fence; print; next }
75+
fence { print; next }
76+
{
77+
n = split($0, p, "`")
78+
out = ""
79+
for (i = 1; i <= n; i++) {
80+
if (i % 2 == 1) {
81+
gsub(/{/, "\\{", p[i])
82+
gsub(/}/, "\\}", p[i])
83+
gsub(/</, "\\&lt;", p[i])
84+
}
85+
out = out p[i]
86+
if (i < n) out = out "`"
87+
}
88+
print out
89+
}
90+
' "$f" > "${f}.tmp" && mv "${f}.tmp" "$f"
91+
done
92+
tools/check-docs-mdx-parse "fern/versions/${version}-content"
7593
echo "Extracted docs from $version"
7694
else
7795
echo "::warning::Tag $version not found — skipping content checkout"

.github/workflows/merge-gate.yaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -296,14 +296,14 @@ jobs:
296296
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
297297
with:
298298
go-version: ${{ steps.versions.outputs.go }}
299-
cache: false
300-
- uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
299+
cache: true
300+
- uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
301301
with:
302302
languages: go
303303
- run: go build ./...
304304
env:
305305
GOFLAGS: -mod=vendor
306-
- uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
306+
- uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
307307

308308
analyze-skip:
309309
needs: [check-paths]
@@ -528,7 +528,7 @@ jobs:
528528
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
529529
with:
530530
go-version: ${{ steps.versions.outputs.go }}
531-
cache: false
531+
cache: true
532532
- name: Verify committed BOM versions and variants match the recipes
533533
env:
534534
GOFLAGS: -mod=vendor
@@ -579,7 +579,7 @@ jobs:
579579
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
580580
with:
581581
go-version: ${{ steps.versions.outputs.go }}
582-
cache: false
582+
cache: true
583583
- name: Verify committed tuning-status table is up to date
584584
env:
585585
GOFLAGS: -mod=vendor
@@ -661,7 +661,7 @@ jobs:
661661
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
662662
with:
663663
go-version: ${{ steps.versions.outputs.go }}
664-
cache: false
664+
cache: true
665665
- name: Verify committed coverage matrix is up to date
666666
env:
667667
GOFLAGS: -mod=vendor

.github/workflows/on-tag.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,7 @@ jobs:
545545

546546
- name: Upload SARIF to GitHub Security
547547
if: hashFiles('results.sarif') != '' || steps.scan.outputs.sarif != ''
548-
uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
548+
uses: github/codeql-action/upload-sarif@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
549549
with:
550550
sarif_file: ${{ steps.scan.outputs.sarif }}
551551
category: 'anchore-image-${{ matrix.image.key }}-${{ matrix.platform.arch }}'

.github/workflows/publish-fern-docs.yml

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -184,14 +184,28 @@ jobs:
184184
if git show-ref --verify --quiet "refs/tags/${version}"; then
185185
mkdir -p "fern/versions/${version}-content"
186186
git archive "refs/tags/${version}" -- docs/ | tar -x --strip-components=1 -C "fern/versions/${version}-content"
187-
# Sanitize frozen markdown for Fern's MDX parser.
188-
# Versioned content gets strict MDX parsing; escape all bare
189-
# { } and < so nothing is interpreted as JSX. Markdown renders
190-
# &lt; as < and \{ as { so the output is visually identical.
191-
find "fern/versions/${version}-content" -name '*.md' -print0 | xargs -0 sed -i \
192-
-e 's/{/\\{/g' \
193-
-e 's/}/\\}/g' \
194-
-e 's/</\&lt;/g'
187+
# Escape {, }, < for MDX — but only outside fenced code blocks and inline code spans
188+
find "fern/versions/${version}-content" -name '*.md' -print0 | while IFS= read -r -d '' f; do
189+
awk '
190+
/^````*/ || /^~~~~*/ { fence = !fence; print; next }
191+
fence { print; next }
192+
{
193+
n = split($0, p, "`")
194+
out = ""
195+
for (i = 1; i <= n; i++) {
196+
if (i % 2 == 1) {
197+
gsub(/{/, "\\{", p[i])
198+
gsub(/}/, "\\}", p[i])
199+
gsub(/</, "\\&lt;", p[i])
200+
}
201+
out = out p[i]
202+
if (i < n) out = out "`"
203+
}
204+
print out
205+
}
206+
' "$f" > "${f}.tmp" && mv "${f}.tmp" "$f"
207+
done
208+
tools/check-docs-mdx-parse "fern/versions/${version}-content"
195209
echo "Extracted docs from $version"
196210
else
197211
echo "::error::Tag $version not found — cannot pin frozen docs content"

.github/workflows/renovate.yaml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,5 +122,18 @@ jobs:
122122
# checksums getting committed alongside a version bump (the version
123123
# changes but the helmfile_checksums/chainsaw_checksums block does
124124
# not), which then breaks E2E/CLI E2E at the sha256 verify step.
125-
RENOVATE_ALLOWED_POST_UPGRADE_COMMANDS: '["^\\./tools/update-[a-z]+-checksums "]'
125+
# `[a-z-]+` (not `[a-z]+`) so hyphenated tool names match too, e.g.
126+
# tools/update-helm-diff-checksums.
127+
#
128+
# The stale-checksum symptom above is not uniform across the three
129+
# tools. A hook that runs but *fails* (renamed asset, egress blip)
130+
# leaves the new version pinned against the old checksums, and only
131+
# helmfile/chainsaw fail a PR-gating job for it — qualification.yaml
132+
# feeds their sha256 into setup-build-tools, which verifies it.
133+
# helm_diff_checksums is read solely by tests/uat/lib/phases.sh, so a
134+
# stale helm-diff pin stays green on every PR check and first surfaces
135+
# in nightly UAT, across all clouds. Renovate's "Artifact update
136+
# problem" warning in the PR body is the signal to heed; helm-diff is
137+
# deliberately not auto-merged, so a human sees it.
138+
RENOVATE_ALLOWED_POST_UPGRADE_COMMANDS: '["^\\./tools/update-[a-z-]+-checksums "]'
126139
LOG_LEVEL: ${{ inputs.logLevel || 'info' }}

0 commit comments

Comments
 (0)