Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
7d42469
feat(check): re-gate cluster-validator per compute-plane targeting an…
rohithb-hub Aug 11, 2026
1ffb0c0
feat(check): detect stale NVCF namespaces and add control-plane valid…
rohithb-hub Aug 11, 2026
dfd490e
feat(check): validate registry credentials before install using gener…
rohithb-hub Aug 11, 2026
1deb005
build(check): update BUILD.bazel for new selfhosted sources and deps
rohithb-hub Aug 11, 2026
4d7c477
fix(check): address code-review findings in cluster-validator and pre…
rohithb-hub Aug 12, 2026
6fa7733
fix(check): replace parseRegistryHostPort with net.SplitHostPort and …
rohithb-hub Aug 12, 2026
b6eaac1
fix(check): sweep ClusterRole and ClusterRoleBinding after validator …
rohithb-hub Aug 12, 2026
5c4d2dc
fix(nvcf-cli): add DaemonSet create/delete RBAC for node-to-node probe
rohithb-hub Aug 17, 2026
df8ac49
Merge branch 'main' into feat/nvcf-cli-cluster-validator
rohithb-hub Aug 18, 2026
8193089
fix(nvcf-cli): address CodeRabbit review comments in validatortag and…
rohithb-hub Aug 19, 2026
c3c4a41
fix(nvcf-cli): restrict NGC token fallback to NGC registries and fix …
rohithb-hub Aug 19, 2026
de7999b
fix(nvcf-cli): response body close, case-insensitive params, empty sc…
rohithb-hub Aug 19, 2026
a52685f
fix(nvcf-cli): authorize realm host before forwarding credentials and…
rohithb-hub Aug 19, 2026
2218f34
fix(nvcf-cli): allow Docker Hub delegated auth realm in token exchange
rohithb-hub Aug 19, 2026
b8faf70
fix(nvcf-cli): stop preflight failing healthy clusters and destroying…
rohithb-hub Sep 15, 2026
ff14b5d
fix(nvcf-cli): scope preflight to the targeted role and stop cross-ro…
rohithb-hub Sep 15, 2026
7aba33b
fix(nvcf-cli): derive stale-namespace probe targets from the stack he…
rohithb-hub Sep 15, 2026
ce9f770
fix(nvcf-cli): make credential and RBAC outcomes consistent across pr…
rohithb-hub Sep 15, 2026
9d6fd31
fix(nvcf-cli): scope validator RBAC per role and quote operator-suppl…
rohithb-hub Sep 15, 2026
1bff24a
style(nvcf-cli): revert unrelated gofmt churn in task and self_hosted…
rohithb-hub Sep 15, 2026
0d35502
fix(nvcf-cli): scope the managed pull secret per validator role
rohithb-hub Sep 15, 2026
3fcd00d
fix(nvcf-cli): verify ownership before mutating or deleting any valid…
rohithb-hub Sep 15, 2026
5952b0f
fix(nvcf-cli): verify RBAC ownership before reuse and reject host-con…
rohithb-hub Sep 15, 2026
96d0865
fix(nvcf-cli): give validator RBAC an unguessable per-run name and re…
rohithb-hub Sep 15, 2026
cbd2c1f
fix(nvcf-cli): require the generated name before deleting any validat…
rohithb-hub Sep 15, 2026
ca3a28c
refactor(nvcf-cli): derive validator label selectors from the managed…
rohithb-hub Sep 15, 2026
46bac78
fix(nvcf-cli): run split-mode preflight only for the roles the flags …
rohithb-hub Sep 15, 2026
95e5648
fix(nvcf-cli): pin preflight remediation hints to the probed kube con…
rohithb-hub Sep 15, 2026
ef693a4
docs(nvcf-cli): document the cluster-validator check flags and regist…
rohithb-hub Sep 15, 2026
1d005b6
Merge remote-tracking branch 'origin/main' into feat/nvcf-cli-cluster…
rohithb-hub Sep 15, 2026
2630d3a
fix(nvcf-cli): pin deletes to the inspected object and always credent…
rohithb-hub Sep 15, 2026
a7475ac
Merge branch 'main' into feat/nvcf-cli-cluster-validator
rohithb-hub Sep 15, 2026
458b201
fix(nvcf-cli): probe the resolved kube context so stale-namespace hin…
rohithb-hub Sep 15, 2026
6c5e2d9
Merge remote-tracking branch 'origin/feat/nvcf-cli-cluster-validator'…
rohithb-hub Sep 15, 2026
8ac688a
Merge branch 'main' into feat/nvcf-cli-cluster-validator
vrv3814 Sep 16, 2026
49ef18a
Merge branch 'main' into feat/nvcf-cli-cluster-validator
vrv3814 Sep 16, 2026
13ddd00
fix(nvcf-cli): stop leaking the NGC key to mirrored registries and re…
rohithb-hub Sep 22, 2026
bb0d56b
fix(nvcf-cli): scope pull-secret adoption per role and stop the stale…
rohithb-hub Sep 22, 2026
e5ab3dd
fix(nvcf-cli): bound the validator Job and make the check command's t…
rohithb-hub Sep 22, 2026
937c88d
docs(nvcf-cli): give isBareRegistryHost and isNGCRegistry their own d…
rohithb-hub Sep 22, 2026
fe418dd
fix(nvcf-cli): scope the validator's pull secret and config to one run
rohithb-hub Sep 22, 2026
3531290
fix(nvcf-cli): preserve the validator Job under --no-cleanup like its…
rohithb-hub Sep 22, 2026
02d5ade
Merge branch 'main' into feat/nvcf-cli-cluster-validator
rohithb-hub Sep 28, 2026
6e32129
fix(nvcf-cli): scope validator cleanup to one run and reclaim every a…
rohithb-hub Sep 29, 2026
d642bef
fix(nvcf-cli): forward the validator's settings and probe the registr…
rohithb-hub Sep 29, 2026
af2c3c3
fix(nvcf-cli): count checks by one rule, scope stale namespaces and S…
rohithb-hub Sep 29, 2026
b375d4b
build(nvcf-cli): register the new validator tests and deps with Bazel
rohithb-hub Sep 29, 2026
ebccad8
Merge remote-tracking branch 'origin/feat/nvcf-cli-cluster-validator'…
rohithb-hub Sep 29, 2026
fbe7599
fix(nvcf-cli): report a validator image without role support as a war…
rohithb-hub Sep 29, 2026
7959067
docs(nvcf-cli): drop a comment reference to the removed prior-Job sweep
rohithb-hub Sep 29, 2026
178461e
fix(nvcf-cli): require positive evidence of a legacy validator, clean…
rohithb-hub Sep 29, 2026
047d62c
fix(nvcf-cli): trim the validator ClusterRole to the verbs its checks…
rohithb-hub Sep 29, 2026
8581a78
fix(nvcf-cli): keep the no-cleanup hint on RBAC failure, forward list…
rohithb-hub Sep 29, 2026
8807222
docs(nvcf-cli): document the validator registry set, probe image, loc…
rohithb-hub Sep 29, 2026
0d240a8
fix(nvcf-cli): fail check when the cluster validator cannot run, and …
rohithb-hub Sep 29, 2026
2672f1e
fix(nvcf-cli): bound the validator cleanup sweeps and end an interrup…
rohithb-hub Sep 29, 2026
bc370f0
fix(nvcf-cli): correct stale validator comments, use severity constan…
rohithb-hub Sep 30, 2026
ff98280
Merge branch 'main' into feat/nvcf-cli-cluster-validator
rohithb-hub Sep 30, 2026
37b3dbe
Merge branch 'main' into feat/nvcf-cli-cluster-validator
rohithb-hub Sep 30, 2026
cca7029
fix(nvcf-cli): pass the stack's NVCF Gateways and HA mode to the vali…
rohithb-hub Sep 30, 2026
3557d52
Merge remote-tracking branch 'origin/feat/nvcf-cli-cluster-validator'…
rohithb-hub Sep 30, 2026
698d4bf
fix(nvcf-cli): grade a spent check budget as a timeout, stop the vali…
rohithb-hub Sep 30, 2026
183583a
fix(nvcf-cli): skip an unresolvable validator tag, prefer the NGC key…
rohithb-hub Sep 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,24 @@ deploy_nvcf:
variables:
KUBECONFIG: $KUBECONFIG_FILE
script:
- nvcf-cli self-hosted check --pre --json | jq -e '.event != "phase_failed"' || exit 2
- nvcf-cli self-hosted check --pre --json 2>&1 >/dev/null | grep '^{' | jq -se 'any(.[]; .event == "final" and .success)' || exit 2
- nvcf-cli self-hosted up --cluster-name=$CLUSTER_NAME --token=$NVCF_ADMIN_JWT --non-interactive --json
- nvcf-cli self-hosted status --json | jq -e '.verdict == "healthy"'
- nvcf-cli self-hosted status --json 2>&1 >/dev/null | grep '^{' | jq -se 'any(.[]; .verdict == "healthy")'
```

Notes:

- `--non-interactive --token=$JWT` is required in CI; never use interactive `init`.
- Always `--json` for machine-parsing.
- `--json` writes JSONL to stderr, not stdout, so redirect with `2>&1 >/dev/null` before a parser.
- `check` reports its verdict in one `final` event, with `success: false` when any check failed at error severity. Gate on that event: `check` never emits `phase_failed`, so a condition on it always passes. Requiring the `final` event also fails the step when the command dies before emitting it.
- Slurp with `jq -s` before testing a condition. Without it `jq -e` takes its exit status from the last event alone.
- stderr also carries plain-text notices, so filter to JSON lines. Do not add `--show-logs` here: it appends a non-JSON transcript to the same stream.
- Final status check gates downstream stages on `verdict == "healthy"`.

## GitOps (Argo / Flux) pattern

CI doesn't `kubectl apply` — instead, render manifests, commit them, let the controller apply.
CI doesn't `kubectl apply`. Instead, render manifests, commit them, let the controller apply.

```yaml
render:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ Stable across subcommands. Use these to drive agent retry / surfacing logic.
|---|---|---|
| `0` | Success | All checks passed; install completed; deployment ACTIVE |
| `1` | Generic error | Helm render failed; network unreachable; file not found; YAML parse error |
| `2` | Pre-flight check failed | Gateway API CRDs missing; kubectl not on PATH; default StorageClass absent |
| `2` | Pre-flight check failed | A `check` result at error severity: Gateway API CRDs missing; kubectl not on PATH; default StorageClass absent; a cluster-validator that could not run (RBAC denied, image pull failure, timeout). Warning-severity results exit `0` |
| `3` | Admin auth failed | No token + `--non-interactive` set; ICMS rejected JWT; init endpoint unreachable |
| `5` | Manifest apply or `--wait` timed out | Helm install timeout; check polled but didn't pass before duration |
| `5` | Manifest apply or `--wait` timed out | Helm install timeout; check polled but didn't pass before duration; the check's time budget ran out before every check ran (those report as not run) |
| `130` | Cancelled by SIGINT/SIGTERM | User Ctrl-C; CI budget exceeded; pod evicted |

## How an agent should react
Expand All @@ -24,4 +24,4 @@ Stable across subcommands. Use these to drive agent retry / surfacing logic.

## Where to find more detail

Every non-zero exit emits a structured `phase_failed` JSON event with `errCategory`, `errMessage`, `remediation` (array), `retryClass` (enum: `none|immediate|backoff|after_remediation|unknown`), `retryAfterSec` (int, optional), and `raw` (subprocess + HTTP + Kubernetes signal). Always consume these in `--json` mode rather than parsing English from stderr.
`check` reports its outcome in one `final` event (`success`, `verdict`, `failedCount`) and never emits `phase_failed`; see `examples/ci-pipelines.md`. A cancellation (exit `130`) emits `final` with `cancelled: true` instead of `phase_failed`; `up` emits `phase_cancelled` before it. Every other non-zero exit emits a structured `phase_failed` JSON event with `errCategory`, `errMessage`, `remediation` (array), `retryClass` (enum: `none|immediate|backoff|after_remediation|unknown`), `retryAfterSec` (int, optional), and `raw` (subprocess + HTTP + Kubernetes signal). Always consume these in `--json` mode rather than parsing English from stderr.
40 changes: 35 additions & 5 deletions ai-tooling/user/skills/nvcf-self-managed-cli/reference/flags.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,49 @@

| Flag | Purpose | Default |
|---|---|---|
| `--control-plane-stack=…` | Control-plane bundle source: local path, git URL, or `oci://` URL | embedded OCI URL pinned by CLI version |
| `--compute-plane-stack=…` | Compute-plane bundle source: local path, git URL, or `oci://` URL | embedded OCI URL pinned by CLI version |
| `--env=local\|prd\|…` | Helmfile environment name | `local` for dev builds, `prd` for releases |
| `--control-plane-stack=...` | Control-plane bundle source: local path, git URL, or `oci://` URL | embedded OCI URL pinned by CLI version |
| `--compute-plane-stack=...` | Compute-plane bundle source: local path, git URL, or `oci://` URL | embedded OCI URL pinned by CLI version |
| `--env=local\|prd\|...` | Helmfile environment name | `local` for dev builds, `prd` for releases |
| `--non-interactive` | Disable all stdin prompts | `false` |
| `--token=$JWT` | Admin JWT, overrides stored session | - |
| `--no-apply` | `install` only - emit YAML, do not kubectl apply | `false` |
| `--output=text\|json` | Legacy alias for `--json` (deprecated, removed in next major) | `text` |
| `--plain` | Force plain streaming output | auto-detect |
| `--wait DURATION` | `check` only; block until pass | - |
| `--wait DURATION` | `check` only; poll until the check passes or DURATION runs out (exit `5`). A warning that is expected to clear, such as a rollout in progress, keeps it polling. Cannot be combined with `--no-cleanup` | - |
| `--control-plane-context CTX` | kubectl context for control plane (REQ-20) | current context |
| `--compute-plane-context CTX` | kubectl context for compute plane (REQ-20) | current context |
| `--icms-url URL` | Public ICMS URL; required when contexts differ | derived from `base_http_url` |
| `--local-only` | `check --pre` only; skip all kubectl contact | `false` |
| `--local-only` | `check` only; run the local-host checks and skip all kubectl contact, whatever scope flag is passed. The CLI prints a note saying so. Env: `NVCF_CLI_SELFHOSTED_LOCAL_ONLY` | `false` |

## `check`-specific

At least one of `--pre`, `--control-plane`, `--compute-plane`, or `--all` is
required. Each selects a role; only the selected roles contact a cluster.

| Flag | Purpose | Default |
|---|---|---|
| `--pre` | Pre-flight: local-host tools plus cluster readiness. Skips SIS reachability, since SIS is not installed yet, unless `--all` or `--compute-plane` is also passed. With `--all`, or with a role's own flag, that role's validator checks the cluster as installed | `false` |
| `--control-plane` | Control-plane checks | `false` |
| `--compute-plane` | Compute-plane checks | `false` |
| `--all` | Every category, including SIS reachability when combined with `--pre` | `false` |
| `--cluster-name NAME` | Cluster name for compute-plane checks | - |
| `--skip-inotify-check` | Skip the per-node inotify-limits probe. Needed when the kubeconfig user cannot create pods in `default`. Env: `NVCF_CLI_SELFHOSTED_SKIP_INOTIFY` | `false` |

### Cluster-validator flags

The validator runs as a Job in the cluster being checked. The CLI creates a
ServiceAccount, ClusterRole, and ClusterRoleBinding for it and removes them
after the run, so the kubeconfig context needs permission to manage those.

| Flag | Purpose | Default |
|---|---|---|
| `--cluster-validator-image REF` | Validator image. Resolution order: flag, `NVCF_CLI_CLUSTER_VALIDATOR_IMAGE`, config key `cluster_validator_image`. A ref with no tag discovers the latest stable tag from the registry; if no tag can be discovered, the probe is skipped with a note to pin one. Unset everywhere skips the probe with a warning | - |
| `--cluster-validator-registries host:port,...` | Extra registries the control-plane validator probes for reachability. They are added to the registries the install pulls from: the validator image's registry, the stack's `global.image.registry`, and `quay.io` for the cert-manager ACME solver unless the stack sets `certManager.acmesolver.image`. `nvcr.io` is probed only when neither the image nor the stack names a registry. The validator dials from a pod with no proxy, so an unreachable registry is a warning; the local credential check is what fails a registry the install cannot pull from. Repeatable or comma-separated. Env: `NVCF_CLI_CLUSTER_VALIDATOR_REGISTRIES`; config key `cluster_validator_registries` | - |
| `--cluster-validator-probe-image REF` | Image for the control-plane validator's node-to-node overlay probe. Needs `sh` and a busybox-style `nc`. Set a mirror for air-gapped clusters. Env: `NVCF_CLI_CLUSTER_VALIDATOR_PROBE_IMAGE`; config key `cluster_validator_probe_image` | `busybox:1.36` from Docker Hub |
| `--cluster-validator-pull-secret NAME` | docker-registry Secret in `default` used to pull the validator image. When empty, the CLI looks for one in the NVCF namespaces and copies it into `default` for the run. Failing that, and only for an image on an NGC registry, it mints one from `NGC_API_KEY` | auto-detect |
| `--skip-cluster-validation` | Skip the in-cluster validator probe entirely. A validator that is configured but cannot run fails the check, so use this to opt out explicitly, for example when the cluster cannot pull the image. Env: `NVCF_CLI_SELFHOSTED_SKIP_CLUSTER_VALIDATION` | `false` |
| `--no-cleanup` | Keep the validator Job, its pod, RBAC, pull secret and ConfigMap for debugging. A later check reclaims them after 24 hours; the result prints the `kubectl delete` command that removes them now | `false` |
| `--show-logs` | Print the validator transcript to stderr after the check events. The transcript is not JSON, and `--json` also writes to stderr, so leave this off when a parser is reading the stream | `false` |

## `up`-specific

Expand Down
43 changes: 40 additions & 3 deletions src/clis/nvcf-cli/.nvcf-cli.yaml.template
Original file line number Diff line number Diff line change
Expand Up @@ -142,20 +142,57 @@ api_keys_owner_id: [email protected]

# Image reference for the cluster-validator pod used by
# `nvcf-cli self-hosted check`. The CLI runs this image as a Job in the
# compute-plane cluster to verify NVCA prerequisites before install.
# cluster being checked to verify prerequisites before install. One image
# serves both roles; the Job environment selects the check set. The
# control-plane checks need an image from an NVCA release that supports
# validator roles; with an older image the CLI reports a warning instead of
# running them.
#
# The Job needs a ServiceAccount, ClusterRole, and ClusterRoleBinding, which
# the CLI creates and removes per run. The kubeconfig context therefore needs
# permission to manage those objects; without it the validator cannot run and
# the check fails. Pass --skip-cluster-validation to run without it.
#
# Config key: cluster_validator_image
# Environment variable: NVCF_CLI_CLUSTER_VALIDATOR_IMAGE
# Command-line flag: --cluster-validator-image
#
# When the value has no tag, the latest tag is discovered from the
# registry (preferring stable releases over rc). If unset everywhere,
# the validator probe is skipped with a warning.
# registry (preferring stable releases over rc). If discovery fails, the
# validator is skipped with a note rather than pulled as :latest; pin a tag
# to avoid depending on discovery. If unset everywhere, the validator probe
# is skipped with a note on stderr.
#
# Staging: stg.nvcr.io/nvidia/nvcf-byoc/cluster-validator
# Prod: nvcr.io/nvidia/nvcf-byoc/cluster-validator
# cluster_validator_image: nvcr.io/nvidia/nvcf-byoc/cluster-validator

# Extra container registries the control-plane validator probes for
# reachability, as host:port. The registries the install pulls from are
# probed already: the validator image's registry, the stack's
# global.image.registry, and quay.io for the cert-manager ACME solver. List
# only registries beyond those.
#
# Config key: cluster_validator_registries (list)
# Environment variable: NVCF_CLI_CLUSTER_VALIDATOR_REGISTRIES (comma-separated)
# Command-line flag: --cluster-validator-registries (repeatable or comma-separated)
#
# cluster_validator_registries:
# - harbor.example.com:443
# - ghcr.io:443

# Image for the control-plane validator's node-to-node overlay probe, which
# needs sh and a busybox-style nc. Defaults to busybox:1.36 from Docker Hub.
# Set a mirror for air-gapped clusters. The probe pods get no imagePullSecrets,
# so the image must be pullable anonymously or through node-level registry
# credentials.
#
# Config key: cluster_validator_probe_image
# Environment variable: NVCF_CLI_CLUSTER_VALIDATOR_PROBE_IMAGE
# Command-line flag: --cluster-validator-probe-image
#
# cluster_validator_probe_image: harbor.example.com/library/busybox:1.36

# ==============================================================================
# General Settings
# ==============================================================================
Expand Down
5 changes: 5 additions & 0 deletions src/clis/nvcf-cli/cmd/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,10 @@ go_test(
"main_test.go",
"registry_test.go",
"root_test.go",
"self_hosted_check_scope_test.go",
"self_hosted_check_stackvalues_test.go",
"self_hosted_check_test.go",
"self_hosted_check_validatorenv_test.go",
"self_hosted_compute_plane_test.go",
"self_hosted_control_plane_test.go",
"self_hosted_down_test.go",
Expand All @@ -141,11 +144,13 @@ go_test(
"//src/clis/nvcf-cli/internal/selfhosted",
"//src/clis/nvcf-cli/internal/selfhosted/auth",
"//src/clis/nvcf-cli/internal/selfhosted/controlplaneprofile",
"//src/clis/nvcf-cli/internal/selfhosted/kubectx",
"//src/clis/nvcf-cli/internal/selfhosted/progress",
"//src/clis/nvcf-cli/internal/selfhosted/reachability",
"//src/clis/nvcf-cli/internal/selfhosted/teardown",
"//src/clis/nvcf-cli/internal/state",
"//src/clis/nvcf-cli/internal/trustbundle",
"@com_github_masterminds_semver_v3//:semver",
"@com_github_spf13_cobra//:cobra",
"@com_github_spf13_pflag//:pflag",
"@com_github_spf13_viper//:viper",
Expand Down
124 changes: 120 additions & 4 deletions src/clis/nvcf-cli/cmd/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,132 @@ package cmd

import (
"context"
"net/http"
"net/http/httptest"
"os"
"testing"

"github.com/spf13/pflag"
"github.com/spf13/viper"

"nvcf-cli/internal/selfhosted"
)

// Default-stub out the validator-tag registry probe so cmd tests don't make
// real network calls or pollute the on-disk cache. Individual tests can
// reassign the variable if they explicitly want to exercise discovery.
// Default-stub every seam that would otherwise reach the developer's cluster or
// the network. Individual tests reassign a variable when they explicitly want
// to exercise that path.
//
// These are not conveniences. Without them `go test ./cmd/` creates a hostPath
// busybox pod per node in `default` (the inotify probe), lists Secrets across
// the stack namespaces of whatever kubeconfig happens to be current, and makes
// an outbound request to nvcr.io per configured registry. That mutates a real
// cluster from a unit test, and it is why the package took minutes and failed
// on a proxied kubeconfig rather than seconds and deterministically.
func TestMain(m *testing.M) {
// Every command reads ~/.nvcf-cli.yaml and some tests save
// ~/.nvcf-cli.state, so a developer's real config would steer results and
// a test run would overwrite their saved credentials.
home, err := os.MkdirTemp("", "nvcf-cli-cmd-test-home-")
if err != nil {
panic(err)
}
_ = os.Setenv("HOME", home)
resolveLatestValidatorTagForSelfHosted = func(_ context.Context, _ string) (string, bool) {
return "", false
}
os.Exit(m.Run())
// Nil prober: the inotify check is skipped rather than creating pods.
Comment on lines 43 to +55

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This TestMain is the right fix, but it is incomplete: newClusterValidatorForSelfHosted and the SIS probe are still real, despite the "Default-stub every seam" comment.

If a developer has NVCF_CLI_CLUSTER_VALIDATOR_IMAGE exported, or cluster_validator_image set in ~/.nvcf-cli.yaml (which the template suggests), TestCheck_SingleClusterMode runs the real validator against the current kube context. That means Secret scans across 11 namespaces, SA/ClusterRole/CRB creation, a Secret minted from NGC_API_KEY, and Jobs, with up to 5 minutes of waiting per role. Reproduced: it POSTs to /api/v1/namespaces/default/serviceaccounts. The --compute-plane and --all tests also GET https://sis.nvcf.nvidia.com/v1/health. Stub both seams, and os.Unsetenv the image variables here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TestMain now stubs newClusterValidatorForSelfHosted and unsets NVCF_CLI_CLUSTER_VALIDATOR_IMAGE, _REGISTRIES and _PROBE_IMAGE (d642bef). An image set in ~/.nvcf-cli.yaml now reaches only the stub, so no check test creates RBAC, Secrets or Jobs. SIS has no seam, so TestMain starts a local stand-in and resetCheckFlags points NVCF_ICMS_URL at it for every check test.

newInotifyProberForSelfHosted = func() selfhosted.NodeInotifyProber { return nil }
// No cluster contact, and a clean result so the category still renders.
newStaleNamespaceProberForSelfHosted = func() selfhosted.StaleNamespaceProber {
return func(context.Context, string, []string) ([]selfhosted.StaleNamespace, error) {
return nil, nil
}
}
// No outbound registry request.
newRegistryCredentialCheckerForSelfHosted = func() selfhosted.RegistryCredentialChecker {
return func(context.Context, string, string, bool) error { return nil }
}
// No validator Job. A developer with NVCF_CLI_CLUSTER_VALIDATOR_IMAGE
// exported, or cluster_validator_image in ~/.nvcf-cli.yaml, would
// otherwise have every check test create RBAC, Secrets and Jobs in the
// current kube context and wait up to five minutes per role.
newClusterValidatorForSelfHosted = func() selfhosted.ClusterValidator {
return func(context.Context, selfhosted.ClusterValidatorParams) selfhosted.ClusterValidatorResult {
return selfhosted.ClusterValidatorResult{Passed: true}
}
}
for _, k := range []string{
"NVCF_CLI_CLUSTER_VALIDATOR_IMAGE", "NVCF_CLI_CLUSTER_VALIDATOR_REGISTRIES",
"NVCF_CLI_CLUSTER_VALIDATOR_PROBE_IMAGE",
} {
_ = os.Unsetenv(k)
}
// The SIS reachability check has no seam, but it resolves its URL from
// NVCF_ICMS_URL, so resetCheckFlags points check tests at this local
// server instead of the real SIS. Scoped per test: setting it for the
// whole package would change what the ICMS URL resolution tests resolve.
sis := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
}))
testSISURL = sis.URL
code := m.Run()
sis.Close()
_ = os.RemoveAll(home)
os.Exit(code)
}

// testSISURL is a local stand-in for SIS, set by TestMain.
var testSISURL string

// resetFlag returns f to its default value and clears its Changed marker. A
// slice flag is replaced, since Set appends once the flag has been set.
func resetFlag(f *pflag.Flag) {
if sv, ok := f.Value.(pflag.SliceValue); ok {
_ = sv.Replace(nil)
} else {
_ = f.Value.Set(f.DefValue)
}
f.Changed = false
}

// resetCheckFlags returns every `self-hosted check` flag to its default,
// including cobra's Changed marker, now and when the test ends. The flag
// variables are package globals that survive rootCmd.Execute, so without this
// a test that passes --pre leaks it into whichever test runs next, and a
// regression guard can pass or fail on test order alone.
func resetCheckFlags(t *testing.T) {
t.Helper()
if testSISURL != "" {
t.Setenv("NVCF_ICMS_URL", testSISURL)
}
reset := func() {
checkPre, checkControlPlane, checkComputePlane, checkAll = false, false, false, false
checkClusterName = ""
Comment on lines +116 to +122

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Round-3 #19/#11 are partly fixed. resetCheckFlags clears cobra's Changed markers but not persistent flag values, and this TestMain doesn't isolate HOME, so check tests are still order- and machine-dependent.

  • Leaked --icms-url: TestCheck_PreSplitRunsBothValidatorRoles passes --icms-url https://sis.example.invalid, and resolveICMSURL prefers the flag. go test -count=1 -shuffle=1 -run 'TestCheck_PreSplitRunsBothValidatorRoles$|TestCheck_SISReachabilityScope$' ./cmd/ fails ("Should not be zero, but was 0"). -run TestCheck_ fails for 8 of 12 seeds.
  • Real config read: a ~/.nvcf-cli.yaml with NVCF_OPENBAO_NAMESPACE set (as in examples/config-dev.yaml:46) fails TestClusterValidatorJobEnv.
  • Real state overwritten: go test ./cmd/ overwrites the real ~/.nvcf-cli.state, because cmd/apikey_test.go writes it. This happened on my machine during this review.
  • Viper override: the cleanup's viper.Set("cluster_validator_probe_image", "") is a top-precedence override that shadows the flag and env for later tests.

Fix: t.Setenv("HOME", t.TempDir()) in TestMain (via os.Setenv), reset flag values from each flag's DefValue, and use viper.Reset() or re-bind rather than viper.Set.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. TestMain points HOME at a temp dir, so ~/.nvcf-cli.yaml is not read and ~/.nvcf-cli.state is not written (I checked the real state file's mtime across a full run). resetCheckFlags now restores each flag's value from DefValue, not only Changed. The probe-image test sets the flag instead of calling viper.Set. Your repro passes, and -run TestCheck_ passes for 12 of 12 shuffle seeds; with the value reset removed, it fails again (183583a).

checkLocalOnly, checkSkipInotifyCheck, checkSkipClusterValidation = false, false, false
checkClusterValidatorImage, checkClusterValidatorPullSecret = "", ""
checkClusterValidatorNoCleanup = false
checkClusterValidatorRegistries = nil
checkClusterValidatorProbeImage = ""
checkShowLogs = false
selfHostedJSON, selfHostedPlain = false, false
selfHostedOutput = "text"
selfHostedWait = ""
selfHostedControlPlaneContext, selfHostedComputePlaneContext = "", ""
// Values too, not only the Changed marker: a test that passes
// --icms-url would otherwise point every later check at its URL.
for _, fs := range []*pflag.FlagSet{selfHostedCheckCmd.Flags(), selfHostedCmd.PersistentFlags()} {
fs.VisitAll(resetFlag)
}
// Other tests call viper.Reset(), which drops the bindings made at
// init, so a flag passed to check would silently not be read.
for key, flag := range map[string]string{
"cluster_validator_image": "cluster-validator-image",
"cluster_validator_registries": "cluster-validator-registries",
"cluster_validator_probe_image": "cluster-validator-probe-image",
} {
_ = viper.BindPFlag(key, selfHostedCheckCmd.Flags().Lookup(flag))
}
}
reset()
t.Cleanup(reset)
}
2 changes: 2 additions & 0 deletions src/clis/nvcf-cli/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,8 @@ func initConfig() {
// swapping --config files. The env name matches the historical
// NVCF_CLI_CLUSTER_VALIDATOR_IMAGE override.
viper.BindEnv("cluster_validator_image", "NVCF_CLI_CLUSTER_VALIDATOR_IMAGE")
viper.BindEnv("cluster_validator_registries", "NVCF_CLI_CLUSTER_VALIDATOR_REGISTRIES")
viper.BindEnv("cluster_validator_probe_image", "NVCF_CLI_CLUSTER_VALIDATOR_PROBE_IMAGE")

// If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil && viper.GetBool("debug") {
Expand Down
Loading
Loading