diff --git a/.claude/skills/containerlab/SKILL.md b/.claude/skills/containerlab/SKILL.md new file mode 100644 index 000000000..a5394e9f3 --- /dev/null +++ b/.claude/skills/containerlab/SKILL.md @@ -0,0 +1,220 @@ +--- +name: containerlab +description: Provision a colima VM (if needed) and deploy a containerlab network device. Defaults to Nokia SRL. Pass a topology file path or image to override. Say "no vm" or "skip vm" to skip VM provisioning. +argument-hint: [no-vm | skip-vm] [--topology ] [--image ] +allowed-tools: [Bash, Read, Write, AskUserQuestion] +allowed-bash: ["colima *", "containerlab *", "docker *", "gnmic *", "curl *"] +--- + +# containerlab + +Provisions a colima VM and deploys a containerlab network device. + +Steps: +1. Setup VM (colima profile: `containerlab`) +2. Install tools (containerlab, gnmic) +3. Deploy network device + +> **No-VM shortcut:** pass `no-vm`, `skip vm`, or `without vm` to skip Steps 1 and 2 and use an already-running VM. + +> **Scope limit:** Only `containerlab` (deploy/inspect/destroy) and `gnmic` (`capabilities` and `get`) may be used. Do not SSH into containerlab nodes, run `docker exec`, modify VM networking or Docker configuration, or make any other changes to the VM or device environment — ask the user first. + +## Arguments + +Parse `$ARGUMENTS` for: +- `no-vm` / `skip vm` / `without vm` → skip Steps 1 and 2 +- `--topology ` → use this topology file instead of the default +- `--image ` → override the device image in the default topology + +If no topology is provided, use the default Nokia SRL topology defined in Step 3. + +## VM command wrapper + +Commands in Steps 2 and 3 run inside the VM. Wrap them as: +``` +VM_EXEC="colima exec -p containerlab --" +$VM_EXEC bash -c "" +``` + +Commands in Step 1 run on the **host** directly (no wrapper). + +The host home directory is mounted at the same path inside the VM — no `cd` needed. + +In no-VM mode, Steps 2 and 3 commands run on the host directly too. + +## Step 1: Setup VM + +> Skip if user passed `no-vm` or similar. + +Check the state of the `containerlab` colima profile: + +```bash +colima list +``` + +- **Running** → check specs match defaults (4 CPU, 8 GB, 60 GB disk). If they differ, warn and ask whether to recreate: + ```bash + colima delete -p containerlab + # then create as below + ``` +- **Stopped** → start it: + ```bash + colima start --profile containerlab --activate=false + ``` +- **Not listed** → create it: + ```bash + colima start --cpu 4 --memory 8 --disk 60 --network-address --profile containerlab --activate=false + ``` + +Verify after start: +```bash +colima list +``` + +## Step 2: Install tools + +Install containerlab if not already present: + +```bash +containerlab version 2>/dev/null || bash -c "$(curl -sL https://get.containerlab.dev)" +``` + +Install gnmic if not already present: + +```bash +gnmic version 2>/dev/null || bash -c "$(curl -sL https://get-gnmic.openconfig.net)" +``` + +## Step 3: Deploy network device + +If the user provided `--topology `, deploy that file directly: + +```bash +containerlab deploy -d -t +``` + +Otherwise, write the default Nokia SRL topology to `/tmp/dev.clab.yml`, substituting `--image` if provided (default: `ghcr.io/nokia/srlinux:26.7.1`): + +```yaml +name: dev-topology + +topology: + nodes: + vjunos: + kind: juniper_vjunosevolved + image: vrnetlab/juniper_vjunosevolved:26.2R1.7-EVO + ports: + - 8001:22 + - 57401:57400 + + nokia_srl: + kind: nokia_srlinux + image: ghcr.io/nokia/srlinux:26.7.1 + startup-config: |- + system name host-name nokia + system grpc-server mgmt yang-models openconfig + ports: + - 8002:22 + - 57402:57400 + + cisco_nxos: + kind: cisco_n9kv + image: vrnetlab/cisco_n9kv:9300-10.6.3-lite + env: + QEMU_MEMORY: 6144 + QEMU_SMP: 2 + startup-config: | + hostname cisco_nxos + feature ospf + feature openconfig + feature grpc + grpc use-vrf management + no ip domain-lookup + ports: + - 8003:22 + - 57403:50051 + + links: [] +``` + +Write it to `/tmp/dev.clab.yml`, then deploy: + +```bash +containerlab deploy -d -t /tmp/dev.clab.yml +``` + +If the container already exists: + +```bash +containerlab deploy -d --reconfigure -t +``` + +Wait for all nodes to be healthy (poll every 15s, up to 10 minutes) using `docker inspect` — more reliable than parsing `containerlab inspect` table output: + +```bash +docker inspect --format '{{.Name}}: {{.State.Health.Status}}' ... +``` + +Repeat until all vrnetlab nodes show `healthy`. Nokia SRL has no health check — it's ready when running. For vrnetlab-based nodes (Juniper, Cisco) this can take 8–15 minutes. + +Once healthy, write a gnmic config file at `/tmp/gnmic-config.yaml` with all nodes from the topology. Use `127.0.0.1` with the forwarded port for each node. For Nokia SRL use `skip-verify: true`; for vrnetlab nodes omit it (plain gRPC). + +Default credentials, ports and TLS settings per node kind: +- Nokia SRL: `admin / NokiaSrl1!`, forwarded port, `skip-verify: true` +- Juniper vJunosEvolved: `admin / admin@123`, forwarded port, `insecure: true` (if the image has TLS configured, use `skip-verify: true` instead) +- Cisco N9Kv: `admin / admin`, forwarded port, `skip-verify: true` + +Example for a topology with all three: + +```yaml +timeout: 10s + +targets: + juniper: + address: 127.0.0.1:57401 + username: admin + password: admin@123 + insecure: true + + srl: + address: 127.0.0.1:57402 + username: admin + password: NokiaSrl1! + skip-verify: true + + nxos: + address: 127.0.0.1:57403 + username: admin + password: admin + skip-verify: true +``` + +Then validate all nodes at once: + +```bash +gnmic --config /tmp/gnmic-config.yaml capabilities +``` + +Show the supported encodings and YANG model count per node. If a node fails, show the error and note it may still be booting. + +## Summary + +Once all nodes are healthy, show the full topology state: + +```bash +containerlab inspect -a +``` + +Print a final summary: +- Colima profile (if used): `containerlab` — running, specs (CPU/memory/disk) +- Containerlab version +- For each node: name, kind, management IP, gNMI endpoint, health state, gnmic capabilities result (encodings supported, YANG model count) +- Next step: run `/netop` to test device + +## References + +- **colima**: https://github.com/abiosoft/colima +- **containerlab**: https://containerlab.dev/cmd/ +- **Nokia SRL containerlab kind**: https://containerlab.dev/manual/kinds/nokia_srlinux/ +- **Juniper vJunos-Evolved containerlab kind**: https://containerlab.dev/manual/kinds/vjunos-evolved/ +- **Cisco NX-OS containerlab kind**: https://containerlab.dev/manual/kinds/cisco_nxos/ diff --git a/.claude/skills/netop/SKILL.md b/.claude/skills/netop/SKILL.md new file mode 100644 index 000000000..87f5238ea --- /dev/null +++ b/.claude/skills/netop/SKILL.md @@ -0,0 +1,325 @@ +--- +name: netop +description: Run local checks (vet, lint, test, gnmi test) and/or manual integration tests against real devices (build, deploy, apply CRs, validate with gnmic). Each phase can be run independently. +argument-hint: [check | test | all] [--device ] [--cr ] [--expected ] +allowed-tools: [Bash, Read, AskUserQuestion] +allowed-bash: ["make vet", "make fmt", "make test", "make test-gnmi", "make lint", "kubectl get *", "kubectl describe *", "kubectl logs *", "kubectl apply *", "kubectl wait *", "kind get *", "gnmic *", "git diff *", "git status", "docker info"] +--- + +# netop + +**Purpose:** You are a tester. Your role is to verify that the operator and its providers work correctly — running checks, deploying to test environments, applying CRs, and validating device state. You read logs, configuration, and test results. You do not change network-operator source code or device state outside of normal operator reconciliation without explicit user approval. + +Two independent phases — run one or both: + +- **check** — local checks: vet, lint, fmt, unit tests, gNMI integration tests (host only, no cluster needed) +- **test** — manual integration test against real devices: observe operator, apply CRs, validate with gnmic + +Parse `$ARGUMENTS`: +- `check` → run Phase 1 only +- `test` → run Phase 2 only +- `all` or no argument → run both phases +- `--device ` → device target for gnmic validation (can be specified multiple times) +- `--cr ` → CR file to apply in Phase 2 +- `--expected ` → expected gnmic state to compare against + +All commands run from the repo root. + +--- + +## Permissions model + +All commands listed in `allowed-bash` run freely — no prompt needed. + +**Requires explicit user approval before executing:** +- Any change to network-operator source code (Go files, types, controllers, webhooks, config) +- Any action that directly modifies device state outside of normal operator reconciliation (e.g. manual gnmic Set, out-of-band config fixes) +- Destructive teardown: `make kind-delete`, `make undeploy-dev`, `kubectl delete` + +**Runs freely in Phase 2 (no approval needed):** +- Creating a kind cluster or deploying the operator (if not present) +- Applying Device CRs +- Applying test CRs + +When one of these is needed, describe exactly what will run and why, then wait for approval before proceeding. + +--- + +## Phase 1: Check + +Runs in order. Each step can be skipped if the user asks for a specific one. + +### Step 1.1: Vet + +```bash +make vet +``` + +Fast static analysis — catches real bugs (bad format strings, unreachable code, suspicious struct tags). +Stop if vet fails — these are likely bugs that need manual fixes. + +### Step 1.2: Lint + +```bash +make lint +``` + +If lint fails, report the issues. Do NOT run `make lint-fix` automatically — ask the user first: +> "Lint found N issues. Run `make lint-fix` to auto-fix some of them — proceed?" + +### Step 1.3: Fmt check (only if vet or lint failed) + +Run `make fmt` only when vet or lint reported failures — formatting drift is only worth surfacing when there are already problems to fix: + +```bash +make fmt +git diff --name-only +``` + +`make fmt` is idempotent — it only reformats files. If `git diff` shows changed files, report them as formatting drift. Do NOT stage or commit the changes — just report. + +Skip this step entirely when both vet and lint passed. + +### Step 1.4: Unit tests + +```bash +make test +``` + +Runs all tests excluding `/e2e` and `/lab`, produces `cover.out`. + +### Step 1.4: gNMI integration tests + +```bash +make test-gnmi +``` + +Builds a fake gNMI server from `test/gnmi/` and runs integration tests against it. Fully standalone — no cluster or VM needed. + +### Phase 1 summary + +``` + Local Check Report + ───────────────────────────────────────────────────── + Vet: ✓ passed (or ✗ N issues) + Lint: ✓ passed (or ✗ N issues) + Fmt: ✓ no drift (or ✗ N files need formatting) ← only shown when vet or lint failed + Unit tests: ✓ N passed, 0 failed (or ✗ list failing tests) + gNMI tests: ✓ N passed, 0 failed (or ✗ list failing testdata files + diff) + ───────────────────────────────────────────────────── + Overall: ✓ all checks passed (or ✗ see above) +``` + +--- + +## Phase 2: Test + +Manual integration test against one or more real devices. + +### Step 2.0: Gather test parameters + +If not provided via `$ARGUMENTS`, ask the user: + +1. **Device(s):** address:port and credentials for each target (e.g. Nokia SRL at `172.20.20.2:57400`, Juniper at `172.20.20.4:57401`). Default credentials for Nokia SRL: `admin / NokiaSrl1!`. +2. **What to test:** which CR kind and what configuration (e.g. "OpenConfig DNS with servers 8.8.8.8 and 1.1.1.1"). If the user points to a sample file, use that. +3. **Expected output:** optional — if provided, compare gnmic response against it. Otherwise infer from the CR spec. + +Store for use in later steps: +``` +DEVICES=( ...) +CREDENTIALS=( ...) +TEST_CR= +EXPECTED= +``` + +### Step 2.1: Observe kind cluster + +First verify Docker is available (regardless of whether it's Docker Desktop, colima, or any other runtime): + +```bash +docker info 2>&1 | head -5 +``` + +If Docker is not running or not accessible, tell the user and stop — do not attempt to start any Docker runtime. + +Then check cluster state — read only: + +```bash +kind get clusters +kubectl get nodes +kubectl get pods -n network-operator-system +``` + +If the `network-operator` cluster does not exist or the operator is not running, proceed with setup automatically. + +### Step 2.1a: Install cert-manager (if not present) + +cert-manager is required before deploying the network operator. Check if it is already installed: + +```bash +kubectl get namespace cert-manager 2>/dev/null +``` + +If not present, install it and wait for it to be ready + +```bash +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.18.2/cert-manager.yaml +kubectl wait --for=condition=Available deployment --all -n cert-manager --timeout=120s +``` + +### Step 2.1b: Build and load the controller image (if cluster was just created or operator not deployed) + +Use a fixed local image tag `network-operator-dev:latest` to avoid colliding with the upstream +`ghcr.io/ironcore-dev/network-operator:latest` image that `config/manager/kustomization.yaml` +maps the `controller` name to by default. + +Build the image: + +```bash +make docker-build IMG=network-operator-dev:latest +``` + +Load it into kind: + +```bash +bin/kind load docker-image network-operator-dev:latest --name network-operator +``` + +### Step 2.2: Observe operator + +Read the current operator state and logs — no changes: + +```bash +kubectl get deployment -n network-operator-system network-operator-controller-manager +kubectl logs -n network-operator-system -l control-plane=controller-manager --tail=50 +``` + +If the operator is not deployed, deploy it: + +```bash +make deploy-dev IMG=network-operator-dev:latest PROVIDER=openconfig KUBECTL="kubectl --context kind-network-operator" +``` + +Then wait for it to be ready: + +```bash +kubectl --context kind-network-operator wait --for=condition=Available deployment/network-operator-controller-manager -n network-operator-system --timeout=120s +``` + +If the operator crashes, check logs before asking the user — do not restart or redeploy without understanding the error. + +### Step 2.3: Observe Device resources + +Read existing Device CRs: + +```bash +kubectl get device -A +kubectl describe device -A +``` + +If no Device CRs exist for the target devices, write the Device CR + Secret manifests to `/tmp/netop-devices.yaml` and apply them: + +```bash +kubectl apply -f /tmp/netop-devices.yaml +``` + +### Step 2.4: Apply test CRs + +If the user has provided a CR to apply, run `kubectl apply -f ` directly. + +Write any generated CR manifests to `/tmp/netop--.yaml` (e.g. `/tmp/netop-banner-vjunos.yaml`) and apply from there: + +```bash +kubectl apply -f /tmp/netop--.yaml +``` + +If the user pointed to a testdata file (`test/gnmi/testdata/`), parse it first (read-only): + +``` +-- / -- + +-- state -- + +``` + +Show the CR YAML section, use the state section as expected output, then apply it. + +After applying (if approved), observe reconciliation — read only: + +```bash +kubectl get -A +kubectl logs -n network-operator-system -l control-plane=controller-manager --tail=50 +``` + +Expect `READY=True`. If not ready within ~30s, show operator logs — do not restart or redeploy. + +### Step 2.5: Validate with gnmic + +For each CR and each device, find the gNMI path from the provider source (`internal/provider/openconfig/.go`, look for `XPath()`), then run: + +```bash +gnmic -a --port -u -p '' --skip-verify --encoding JSON_IETF get --path '' +``` + +Always print the full JSON response without truncation. This is read-only — no gnmic Set calls. + +**Validation:** +- Expected output provided → compare field by field +- No expected output → infer expected values from the CR spec and validate those fields + +### Phase 2 summary + +Print a structured report after all CRs are validated. + +For each CR tested: +1. Applied YAML: `kubectl get -n -o yaml` +2. gnmic command and full JSON response +3. Relevant operator logs: `kubectl logs ... | grep -i '\|error\|warn'` + +End with a summary table: + +``` + Test Report + ┌──────────────┬──────────┬───────────┬───────┬──────────────────────────────┬──────────────────┬─────────────────┐ + │ CR Name │ Kind │ Namespace │ Ready │ gNMI Path │ Device │ Result │ + ├──────────────┼──────────┼───────────┼───────┼──────────────────────────────┼──────────────────┼─────────────────┤ + │ dns │ DNS │ default │ True │ openconfig-system:system/dns │ 172.20.20.2:57400│ ✓ value matches │ + │ dns │ DNS │ default │ True │ openconfig-system:system/dns │ 172.20.20.4:57401│ ✓ value matches │ + └──────────────┴──────────┴───────────┴───────┴──────────────────────────────┴──────────────────┴─────────────────┘ +``` + +Results: +- `✓ value matches` — gnmic response matches expected/inferred value +- `✗ mismatch` — differs (show diff inline below table) +- `✗ not found` — gnmic returned empty or error + +--- + +## Cleanup + +Only perform cleanup steps when the user explicitly asks. Always ask for confirmation before any destructive action. + +Undeploy the operator: + +```bash +make undeploy-dev PROVIDER= +``` + +Delete the kind cluster: + +```bash +make kind-delete +``` + +--- + +## References + +- **go vet**: https://pkg.go.dev/cmd/vet +- **golangci-lint**: https://golangci-lint.run +- **gnmic**: https://gnmic.openconfig.net +- **kind**: https://kind.sigs.k8s.io/docs/user/quick-start/ +- **kubectl**: https://kubernetes.io/docs/reference/kubectl/ +- **OpenConfig YANG schemas**: https://openconfig.net/projects/models/schemadocs/ +- **Juniper vJunos-Evolved**: https://www.juniper.net/documentation/us/en/software/vjunos/vjunos-evolved/ +- **Cisco NX-OS gNMI**: https://developer.cisco.com/docs/nx-os/#!using-gnmi diff --git a/Makefile b/Makefile index 4e2c09127..177dc4492 100644 --- a/Makefile +++ b/Makefile @@ -241,10 +241,18 @@ deploy: manifests kustomize ## Deploy controller to the K8s cluster. fi $(KUSTOMIZE) build config/default | $(KUBECTL) apply -f - +.PHONY: deploy-dev +deploy-dev: manifests kustomize ## Deploy controller using config/develop overlay. Use PROVIDER to set the provider (default: openconfig). + IMG=$(IMG) PROVIDER=$(PROVIDER) $(KUSTOMIZE) build config/develop | envsubst | $(KUBECTL) apply -f - + .PHONY: undeploy undeploy: kustomize ## Undeploy controller from the K8s cluster. Call with ignore-not-found=true to ignore resource not found errors during deletion. $(KUSTOMIZE) build config/default | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f - +.PHONY: undeploy-dev +undeploy-dev: kustomize ## Undeploy controller using config/develop overlay. + PROVIDER=$(PROVIDER) $(KUSTOMIZE) build config/develop | envsubst | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f - + ##@ Dependencies ## Location to install dependencies to diff --git a/README.md b/README.md index ff09d614b..a34ef21f0 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,127 @@ Users can just run kubectl apply -f to install the project kubectl apply -f https://raw.githubusercontent.com//network-operator//dist/install.yaml ``` +## Claude Code Skills + +This project includes [Claude Code](https://claude.ai/code) skills for interactive development workflows. Skills are located in `.claude/skills/` and invoked via slash commands. + +### `/netop-setup` + +Set up the full test environment (colima VM, kind cluster, cert-manager, containerlab device): + +``` +/netop-setup +``` + +Say `no vm` or `skip vm` to skip VM provisioning if it's already running. Supports colima and multipass. + +**Example — Nokia SRL:** + +``` +/netop-setup +``` + +``` + Colima VM network-operator — arm64, 4 CPU, 8 GB, 60 GB, running + Kind cluster network-operator — Kubernetes v1.36.1, node Ready + cert-manager v1.18.2 — all deployments available + Nokia SRL — clab-srlceos01-srl running at 172.20.20.2 + + Next step: run /netop-test. +``` + +**Example — Cisco device accessible on `127.0.0.1:57400`:** + +``` +/netop-setup cisco device accessible on 127.0.0.1:57400 with user admin and password admin +``` + +``` + Colima VM network-operator — arm64, 4 CPU, 8 GB, 60 GB, running + Kind cluster network-operator — Kubernetes v1.36.1, node Ready + cert-manager v1.18.2 — all deployments available + Cisco device — reachable from VM at 192.168.5.2:57400 (Mac host IP as seen from VM — use this instead of 127.0.0.1) + + Next step: run /netop-test with GNMI_TARGET=192.168.5.2:57400. +``` + +### `/netop-test` + +Build, deploy and test the operator against a real containerlab device: + +``` +/netop-test +``` + +You can pass CR files and expected results directly: + +``` +/netop-test @config/samples/v1alpha1_banner.yaml +/netop-test @test/gnmi/testdata/openconfig/banner.txt +/netop-test @my-cr.yaml @my-expected.json +``` + +- **`config/samples/`** — ready-made sample CRs for all supported resource types +- **`test/gnmi/testdata/`** — testdata files containing both the CR YAML and expected gnmic state in one file (parsed automatically) +- **Custom files** — pass any CR YAML and optionally a JSON file with the expected gnmic state + +You can also ask the skill to inspect the environment or show device capabilities in natural language: + +``` +/netop-test show me the device topology +/netop-test show me device capabilities +/netop-test show me device configuration openconfig-system:system/dns +``` + +- `show me the device topology` → runs `containerlab inspect -a` to list all running labs and their node IPs +- `show me device capabilities` → runs `gnmic capabilities` to show supported YANG models, encodings, and gNMI version +- `show me device configuration ` → runs `gnmic get --path ` and prints the full JSON response + +**Example output:** + +``` + Test Report + + ┌──────────────┬──────────────┬───────────┬───────┬──────────────────────────────────────────────┬─────────────────┐ + │ CR Name │ Kind │ Namespace │ Ready │ gNMI Path │ Result │ + ├──────────────┼──────────────┼───────────┼───────┼──────────────────────────────────────────────┼─────────────────┤ + │ banner │ Banner │ default │ True │ openconfig-system:system/config/login-banner │ ✓ value matches │ + └──────────────┴──────────────┴───────────┴───────┴──────────────────────────────────────────────┴─────────────────┘ + + gnmic get openconfig-system:system/config/login-banner + ────────────────────────────────────────────────────── + { + "openconfig-system:system": { + "config": { + "login-banner": "###################################################\n# WARNING: Unauthorized access is prohibited. #\n###################################################\n" + } + } + } +``` + +### `/netop-check` + +Run local checks before committing or opening a PR: + +``` +/netop-check +``` + +Runs `go vet`, `golangci-lint`, unit tests (`make test`), and gNMI integration tests (`make test-gnmi`). If lint fails, offers to run `make fmt` or `make lint-fix` to auto-fix issues. Prints a summary report at the end. + +**Example output:** + +``` + Local Dev Report + ──────────────────────────────────────────────────────── + Vet: ✓ passed + Lint: ✓ passed + Unit tests: ✓ 15 packages passed, 0 failed + gNMI tests: ✓ N passed, 0 failed + ──────────────────────────────────────────────────────── + Overall: ✓ all checks passed +``` + ## Support, Feedback, Contributing This project is open to feature requests/suggestions, bug reports etc. via [GitHub issues](https://github.com/ironcore-dev/network-operator/issues). Contribution and feedback are encouraged and always welcome. For more information about how to contribute, the project structure, as well as additional contribution information, see our [Contribution Guidelines](CONTRIBUTING.md). diff --git a/config/develop/manager_patch.yaml b/config/develop/manager_patch.yaml index 9f18a099a..4f87b4502 100644 --- a/config/develop/manager_patch.yaml +++ b/config/develop/manager_patch.yaml @@ -1,10 +1,13 @@ +- op: replace + path: /spec/template/spec/containers/0/image + value: ${IMG} - op: replace path: /spec/template/spec/containers/0/args value: - --leader-elect=false - --health-probe-bind-address=:8081 - --metrics-bind-address=:8443 - - --provider=openconfig + - --provider=${PROVIDER} - --requeue-interval=30s - --max-concurrent-reconciles=5 - --zap-log-level=3