Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
26 changes: 15 additions & 11 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ If a doc above is silent on a question you need to answer, say so explicitly rat
## Three components, three toolchains

- **`operator/`** — Go controller-manager (controller-runtime, Kubebuilder v4). Go 1.26.5, vendored (`GOFLAGS=-mod=vendor`). Also hosts the CLI (`cmd/cli`) built as a kubectl plugin.
- **`agent/skyhook-agent/`** — Python 3.10+ package (hatch-managed). Runs inside every package container; reads `/skyhook-package/config.json` and executes lifecycle steps (apply / config / interrupt / post-interrupt / upgrade / uninstall). Tests via pytest, vendored deps under `agent/vendor/`.
- **`agent/`** — Go module (`github.com/NVIDIA/nodewright/agent`), Go 1.27, vendored. A single static binary that runs inside every package container; reads `/skyhook-package/config.json` and executes lifecycle steps (apply / config / interrupt / post-interrupt / upgrade / uninstall). Tests via Ginkgo/Gomega; tooling is installed into `agent/bin/` by `agent/deps.mk`.
- **`chart/`** — Helm chart. Generated from `operator/config/` via `helmify` (`make generate-helm`) but hand-edited after; don't regenerate blindly.

- **`tools/`**: Go module for release tooling, holding `cmd/openvex` (binds `.openvex.json` to a platform digest and validates it against the OpenVEX v0.2.0 contract) and `tests/releasepolicy`, a nested module pinning the release evidence policy. Two rules: it stays **stdlib-only**, because the release job runs `go run ./cmd/openvex` with a toolchain and no module download, so a dependency turns every release into a fetch; and its tests are **stdlib `testing`**, not Ginkgo. The policy tests live in their own nested module precisely so a YAML parser can be used there without reaching the release path.
Expand Down Expand Up @@ -91,9 +91,13 @@ go test -mod=vendor -run TestName ./internal/graph/...
Agent commands run from `agent/`:

```bash
make venv # one-time: create ./venv and install hatch
make test # hatch test with coverage
make build # hatch build → dist/
make test lint # exactly what CI's agent lanes run
make test # ginkgo unit tests with coverage, writes to reporting/
make lint # golangci-lint + license-header check
make build # agent binary → bin/agent
make fmt # gofmt + license headers
make generate-mocks # regenerate mockery mocks — REQUIRED after editing mocked interfaces
make docker-build # local container image from containers/agent.Dockerfile
```

E2E tests use [chainsaw](https://kyverno.github.io/chainsaw/) against a real cluster, driven from `k8s-tests/chainsaw/{skyhook,cli,helm,deployment-policy}`. They require a kind cluster set up via `make create-kind-cluster` (or the 15-node variant for deployment-policy). `operator-agent-tests` additionally requires `AGENT_IMAGE=…` to be set.
Expand All @@ -102,7 +106,7 @@ E2E tests use [chainsaw](https://kyverno.github.io/chainsaw/) against a real clu

Run the make targets CI runs, on your machine, before you push. This applies to a coding agent exactly as it does to a person.

- **CI runs the Makefile, so you should too.** The operator workflow runs `make $MAKE_TARGETS` from `operator/`, and its unit lane is `vet lint unit-tests`. `make vet lint unit-tests` locally is the gate itself, not an approximation of it. Run all three: `make unit-tests` alone does not lint, only `make test` (the heavy full suite) pulls in `fmt vet lint`. For the agent, it is `make test` from `agent/`. Docs-only changes need none of this.
- **CI runs the Makefile, so you should too.** The operator workflow runs `make $MAKE_TARGETS` from `operator/`, and its unit lane is `vet lint unit-tests`. `make vet lint unit-tests` locally is the gate itself, not an approximation of it. Run all three: `make unit-tests` alone does not lint, only `make test` (the heavy full suite) pulls in `fmt vet lint`. For the agent, it is `make test lint` from `agent/`. Docs-only changes need none of this.
- **Never reach for raw `go test` / `go build` / `golangci-lint run` instead.** The targets pass `-mod=vendor`, apply license headers, sequence CRD/deepcopy/mock generation, and install most of their own dependencies (envtest, ginkgo, golangci-lint) into `operator/bin/`. A missing tool is not a reason to skip a suite; it means the target was bypassed. `make help` lists the targets.
- **Unit tests and lint need nothing external. The e2e suites need a working `kind` and a running container runtime, and the Makefile installs neither.** The kind version is pinned in `operator/versions.yaml`; `DOCKER_CMD` defaults to `docker`, pass `DOCKER_CMD=podman` otherwise. ctlptl and chainsaw are downloaded for you. `make test` additionally needs `AGENT_IMAGE` set to the pin in `chart/values.yaml`: it defaults to an agentless image that passes the agent suite without executing anything.
- **On a fork this is the fast path, not the slow one.** Workflow runs from a fork wait for a maintainer to approve them by hand, so pushing to see what CI says can cost hours where the same checks locally cost minutes.
Expand Down Expand Up @@ -149,17 +153,17 @@ Packages run through stages in this order (from `README.md` §Stages):

Semantic versioning is strictly enforced so the operator can detect upgrade vs. downgrade vs. fresh-apply. State is persisted as annotations on the Node (`nodewright.nvidia.com/nodeState_<name>`, where `<name>` is the CR's `metadata.name`), not on the NodeWright CR.

### Agent (Python)
### Agent (Go, `agent/`)

The agent is a container entrypoint the operator injects alongside every package. It:

- Reads `/skyhook-package/config.json` (validated against `schemas/`)
- Dispatches to the requested stage/step
- Uses `chroot_exec.py` to run step scripts inside the host root mount
- Writes completion flag files so subsequent stages skip already-done work
- Reads `/skyhook-package/config.json` (validated against the JSON schemas embedded from `internal/config/schemas/`)
- Dispatches to the requested stage/step (`internal/agent`, with `internal/step` and `internal/interrupts` owning the per-step and per-interrupt behaviour)
- Runs step scripts through `internal/command`, inside a chroot of the host root mount, or against the agent's own filesystem for `on_host: false`
- Writes completion flag files (`internal/flags`) so subsequent stages skip already-done work. Both the legacy marker name and a fingerprint marker are written, so state is honoured across `agent/v6.x` (Python) and `v7.x` (Go) in either direction, with the one exception (the Go-only pending `node_restart` marker) recorded in `agent/RELEASE_NOTES.md`
- Gates interrupt re-runs on `SKYHOOK_RESOURCE_ID` (unique per package config)

Relevant env vars are documented in `agent/README.md`.
The agent logs with `log/slog`; the `logr` rule in the Go style section below is about the operator. Relevant env vars are documented in `agent/README.md`.

### CLI (`operator/cmd/cli/`)

Expand Down
13 changes: 5 additions & 8 deletions .claude/skills/nodewright-managing-openvex/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,12 @@ these images most findings are **not** direct dependencies. Check the artifact
type in the scan output first (`.matches[].artifact.type`) and follow it:

```bash
# Go modules (artifact.type == "go-module"): operator, and the Go agent.
grep -n '<module-path>' operator/go.mod agent/go/go.mod
git log --oneline -5 -- operator/go.mod agent/go/go.mod
# Go modules (artifact.type == "go-module"): operator and agent.
grep -n '<module-path>' operator/go.mod agent/go.mod
git log --oneline -5 -- operator/go.mod agent/go.mod

# Python packages (artifact.type == "python") in the agent venv.
grep -rn '<package-name>' agent/skyhook-agent/pyproject.toml agent/vendor/

# deb packages and the CPython binary (artifact.type == "deb" or "binary") come
# from the base image, not from our source. Nothing in this repo pins their
# Base-image contents (artifact.type == "deb" or "binary") come from the
# distroless base, not from our source. Nothing in this repo pins their
# versions: `scripts/latest-distroless.sh` resolves the newest base at build
# time, so "is it fixed on main?" means "does a newer base carry the fix?".
grep -n 'DISTROLESS_VERSION\|FROM nvcr.io' containers/agent.Dockerfile containers/operator.Dockerfile
Expand Down
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
## How this was verified
<!-- Which tests and linters did you run, and what did you not run? -->
<!-- Go code changed: `make vet lint unit-tests` from operator/ is the minimum. That is exactly what CI's unit lane runs, and note that `make unit-tests` on its own does NOT run the linter. -->
<!-- Agent (Python) changed: `make test` from agent/. -->
<!-- Agent changed: `make test lint` from agent/, which is what CI's agent lanes run. -->
<!-- Docs, comments or other non-code changes only: say so, that is a complete answer. -->
<!-- Could not run something (no cluster for e2e, for example)? Say which and why. -->

Expand Down
4 changes: 2 additions & 2 deletions .github/actions/dump-operator-agent-diagnostics/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
name: Dump operator-agent diagnostics
description: >
Dump agent pod logs and the agent's on-node state after an operator-agent
chainsaw failure. Shared by the Python and Go suites so a parity failure is
diagnosed from the same evidence on both sides.
chainsaw failure. Shared by every workflow that runs the suite (agent-ci and
operator-ci) so a failure is diagnosed from the same evidence wherever it runs.

inputs:
node:
Expand Down
15 changes: 2 additions & 13 deletions .github/renovate.json5
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
{
// Dependency notices are release artifacts. Refresh them once after all
// updates on a branch, and commit only the three generated outputs.
matchManagers: ["gomod", "pep621"],
matchManagers: ["gomod"],
postUpgradeTasks: {
commands: ["make notices"],
fileFilters: [
Expand Down Expand Up @@ -111,20 +111,9 @@
},
{
matchManagers: ["gomod"],
matchFileNames: ["agent/go/**"],
addLabels: ["component/agent"],
},
{
matchManagers: ["pep621"],
matchFileNames: ["agent/skyhook-agent/**"],
matchFileNames: ["agent/**"],
addLabels: ["component/agent"],
},
{
// The Python compatibility floor is a support decision, not a dependency.
matchManagers: ["pep621"],
matchDepTypes: ["requires-python"],
enabled: false,
},
{
matchManagers: ["dockerfile"],
matchFileNames: ["containers/**"],
Expand Down
Loading
Loading