From 2843b90ccd8fbff7e1a4837653fb9ae203654f78 Mon Sep 17 00:00:00 2001 From: Robert Gildein Date: Tue, 11 Aug 2026 16:11:27 +0200 Subject: [PATCH 1/2] feat(agentic): Add Claude skills for local dev and testing Add 3 skills for local dev and testing: 1. netop-check: run lint, unit and gnmic tests with summary output - capable to reformat code if needed 2. netop-setup: configure VM with all tools, k8s cluster and Nokia device with summary output - using colima to create VM, but can use another tools like multipass, ... - can be run without creating VM, for direct use on Linux machine - can omit creating device in containerlab if user provided connection to existing one 3. netop-test: to run manual test - deploy custom CRDs and verify that configuration was properly set via gnmic Signed-off-by: Robert Gildein --- .claude/skills/netop-check/SKILL.md | 105 +++++++++++ .claude/skills/netop-setup/SKILL.md | 249 +++++++++++++++++++++++++ .claude/skills/netop-test/SKILL.md | 279 ++++++++++++++++++++++++++++ README.md | 121 ++++++++++++ 4 files changed, 754 insertions(+) create mode 100644 .claude/skills/netop-check/SKILL.md create mode 100644 .claude/skills/netop-setup/SKILL.md create mode 100644 .claude/skills/netop-test/SKILL.md diff --git a/.claude/skills/netop-check/SKILL.md b/.claude/skills/netop-check/SKILL.md new file mode 100644 index 000000000..8f563d7dc --- /dev/null +++ b/.claude/skills/netop-check/SKILL.md @@ -0,0 +1,105 @@ +--- +name: netop-check +description: Run local development checks for network-operator — lint, unit tests, and gNMI integration tests. Use before committing or opening a PR. All commands run on the host machine (no VM needed). +argument-hint: [lint | test | all] +allowed-tools: [Bash, Read, AskUserQuestion] +--- + +# netop-check + +Runs local development checks: +1. Vet (go vet — fast static analysis) +2. Lint (golangci-lint) +3. Unit tests +4. gNMI functional tests + +All commands run directly on the host machine in the repo root. + +## Current changes + +```bash +git diff HEAD +``` + +## Instructions + +Run the phases below in order, or just the one the user asked for via `$ARGUMENTS`. + +### Step 1: Vet + +```bash +make vet +``` + +`go vet` catches real bugs — incorrect format strings, unreachable code, suspicious struct tags, etc. It's fast and should always pass. + +If vet **fails** → show the errors and stop. These are likely bugs that need manual fixes before proceeding. + +### Step 2: Lint + +```bash +make lint +``` + +If lint **passes** → report success and continue. + +If lint **fails** → show the errors and ask the user which fix to try: + +- `make fmt` — fixes import ordering and formatting (goimports + gofumpt), style only +- `make lint-fix` — runs golangci-lint with `--fix`, auto-fixes some lint issues beyond formatting +- Both — run `make fmt` first, then `make lint-fix` + +Then re-run `make lint` to confirm the remaining errors (if any) need manual fixes. + +> **Note:** Neither command resolves logic or type errors — those need manual fixes. + +### Step 3: Unit tests + +```bash +make test +``` + +This runs all tests excluding `/e2e` and `/lab` subdirectories and produces `cover.out`. + +If tests fail → show the failing test names and error output. + +### Step 4: gNMI integration tests + +```bash +make test-gnmi +``` + +This builds and runs the fake gNMI server from `test/gnmi/` and executes the integration tests against it. Fully standalone — no kind cluster or VM needed. + +If tests fail → show the failing testdata files and the diff between expected and actual state. + +### Summary + +After all phases complete, print a report: + +``` + Local Dev Report + ──────────────────────────────────────────────────────── + Vet: ✓ passed (or ✗ N issues — list them) + Lint: ✓ passed (or ✗ N issues — list them) + (fmt offered: yes/no) + Unit tests: ✓ N passed, 0 failed (or ✗ N failed — list failing tests) + gNMI tests: ✓ N passed, 0 failed (or ✗ N failed — list failing testdata files) + ──────────────────────────────────────────────────────── + Overall: ✓ all checks passed (or ✗ see above) +``` + +For gNMI test failures, show the diff between expected and actual state: +``` + FAIL: testdata/openconfig/banner.txt + Expected: {"openconfig-system:system":{"config":{"login-banner":"..."}}} + Actual: {} +``` + +## References + +- [go vet](https://pkg.go.dev/cmd/vet) — static analysis tool built into Go +- [golangci-lint](https://golangci-lint.run) — aggregated linter runner (custom build used here via `.custom-gcl.yaml`) +- [goimports](https://pkg.go.dev/golang.org/x/tools/cmd/goimports) — fixes import grouping and formatting (`make fmt`) +- [gofumpt](https://github.com/mvdan/gofumpt) — stricter gofmt, run alongside goimports (`make fmt`) +- [gnmic](https://gnmic.openconfig.net) — gNMI CLI client used for validation diff --git a/.claude/skills/netop-setup/SKILL.md b/.claude/skills/netop-setup/SKILL.md new file mode 100644 index 000000000..3b12cf878 --- /dev/null +++ b/.claude/skills/netop-setup/SKILL.md @@ -0,0 +1,249 @@ +--- +name: netop-setup +description: One-time setup of the network-operator test environment. Provisions a colima VM, creates a kind cluster with cert-manager, and deploys a containerlab network device. Use this before the first test session or after a full teardown. Say "no vm" or "skip vm" to skip the VM provisioning step. +argument-hint: [no-vm | skip-vm] +allowed-tools: [Bash, Read, Write, AskUserQuestion] +--- + +# netop-setup + +Sets up the full test environment for network-operator from scratch: +1. Setup VM (profile: `network-operator`) +2. Install tools in VM +3. Kind cluster + cert-manager +4. Containerlab network device + +> **No-VM shortcut:** pass `no-vm`, `skip vm`, `without vm`, or `run without VM` to skip Steps 1 and 2. + +## Environment + +At the start of the session, ask the user which provider they plan to test (if not already known from `$ARGUMENTS`): +- **openconfig / Nokia SRL** → `PROVIDER=openconfig` +- **cisco** → `PROVIDER=cisco` + +Also ask which VM tool they are using (default: colima): +- **colima** → `VM_EXEC="colima exec -p network-operator --"` +- **multipass** → `VM_EXEC="multipass exec network-operator --"` + +These variables are used in every command below: + +``` +PROVIDER=openconfig # or cisco +VM_EXEC="colima exec -p network-operator --" # or multipass exec network-operator -- +``` + +`LOCALBIN` is set persistently in the VM's `~/.bashrc` during Step 2 — no need to prefix it on any `make` command. + +> **No-VM case:** `LOCALBIN` is not set. The Makefile default (`./bin`) applies automatically. + +The VM wrapper for all Step 2+ commands is: +```bash +$VM_EXEC bash -c "" +``` + +> The host home directory is mounted at the same path inside the VM — commands run from the same directory as on the host, so no `cd` is needed. + +> All commands in Steps 2, 3, and 4 use this wrapper. It is not repeated in each step — just substitute `` with the bare command shown. + +## Step 1: Setup VM + +> **Skip Steps 1 and 2** if the user passes `no-vm` or any similar phrasing. + +Check the state of the `network-operator` colima profile: + +```bash +colima list +``` + +- **Running** → check specs match defaults (4 CPU, 8 GB, 60 GB disk). If they differ, warn the user and ask if they want to recreate: + ```bash + colima delete -p network-operator + # then create as below + ``` +- **Stopped** → start it: + ```bash + colima start --profile network-operator + ``` +- **Not listed** → create it: + ```bash + colima start --cpu 4 --memory 8 --disk 60 --network-address --profile network-operator + ``` + +Verify after start: +```bash +colima list +``` + +Ensure `~/.local/bin` exists, is on PATH, and `LOCALBIN` is exported in the VM: + +```bash +mkdir -p ~/.local/bin +grep -qxF 'export LOCALBIN="$HOME/.local/bin"' ~/.bashrc || echo 'export LOCALBIN="$HOME/.local/bin"' >> ~/.bashrc +grep -qxF 'export PATH="$LOCALBIN:$PATH"' ~/.bashrc || echo 'export PATH="$LOCALBIN:$PATH"' >> ~/.bashrc +export LOCALBIN="$HOME/.local/bin" +export PATH="$LOCALBIN:$PATH" +``` + +## Step 2: Install tools in VM + +> **Skip this step** if the user is running without a VM. + +Install required tools if not already present: + +```bash +sudo apt-get update -qq +sudo apt-get install -y make curl jq vim snapd +which yq || sudo snap install yq +which go || sudo snap install go --classic +which kubectl || sudo snap install kubectl --classic +which k || sudo snap alias kubectl k +which gnmic || bash -c "$(curl -sL https://get-gnmic.openconfig.net)" +``` + +## Step 3: Kind cluster + cert-manager + +```bash +make kind +make kind-create +``` + +Wait for node ready: +```bash +kubectl wait --for=condition=Ready node --all --timeout=120s +``` + +Install cert-manager: +```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 +``` + +Verify: +```bash +kubectl get nodes +kubectl get pods -n cert-manager +``` + +## Step 4: Containerlab device + +Ask the user which device type to use: + +**Option A — Nokia SRL (default, arm64-compatible)** +**Option B — Remote Cisco device (team cloud via SSH port forwarding)** + +### Option A: Nokia SRL + +Check if containerlab is installed: +```bash +containerlab version 2>/dev/null || bash -c "$(curl -sL https://get.containerlab.dev)" +``` + +Write the topology file to `/tmp/srl01.clab.yml`: + +```yaml +name: srlceos01 + +topology: + nodes: + srl: + kind: nokia_srlinux + image: ghcr.io/nokia/srlinux:26.7.1 + startup-config: |- + system name host-name srl + system grpc-server mgmt yang-models openconfig + ports: + - 57022:22 + - 57400:57400 + + links: + - endpoints: ["srl:ethernet-1/1", "srl:ethernet-1/2"] +``` + +Deploy: +```bash +containerlab deploy -d -t /tmp/srl01.clab.yml +``` + +If the container already exists or the user wants to reconfigure: +```bash +containerlab deploy -d --reconfigure -t /tmp/srl01.clab.yml +``` + +Wait until running: +```bash +docker inspect -f '{{.State.Status}}' clab-srlceos01-srl +``` + +Show device IP: +```bash +containerlab inspect -t /tmp/srl01.clab.yml +``` + +The Nokia SRL management IP is typically `172.20.20.2` — confirm and note it for `/netop-test`. + +### Option B: Remote Cisco device + +> **Note:** Local Cisco N9Kv deployment is not possible on Apple Silicon — nested virtualization required for QEMU x86 emulation is not supported. Use a remote device instead (direct access or via SSH port forwarding — that's the user's responsibility). + +Ask the user for the device connection details: +- `CISCO_IP` — IP address reachable from the VM (e.g. `10.0.0.5` or `127.0.0.1` if port-forwarded) +- `CISCO_PORT` — gNMI port (default: `57400`) +- `CISCO_USER` — gNMI username (default: `admin`) +- `CISCO_PASSWORD` — gNMI password + +Verify connectivity from inside the VM: +```bash +nc -z $CISCO_IP $CISCO_PORT && echo "device reachable" || echo "device not reachable — check IP, port, and any required port forwarding" +``` + +> **Localhost warning:** If the user provides `127.0.0.1` or `localhost` as `CISCO_IP`, warn them that this refers to the VM itself, not the Mac host. Detect the Mac host IP as seen from the VM (its default gateway) and use that instead: +> ```bash +> HOST_IP=$(ip route | awk '/default/ {print $3}') +> echo "Use $HOST_IP instead of 127.0.0.1" +> ``` +> Update `CISCO_IP` to `$HOST_IP` before proceeding. + +Note these values for `/netop-test`: +``` +GNMI_TARGET=$CISCO_IP:$CISCO_PORT +GNMI_USER=$CISCO_USER +GNMI_PASSWORD=$CISCO_PASSWORD +``` + +## Summary + +Run the following to show the full state of the dev environment: + +```bash +# Docker version +docker --version + +# Kubernetes cluster version and nodes +kubectl version +kubectl get nodes + +# All pods (wait until ready) +kubectl wait --for=condition=Ready pod --all -A --timeout=120s && kubectl get pods -A + +# Containerlab device status +containerlab inspect -a +``` + +Print a final summary: +- Colima profile: `network-operator` (CPU, memory, disk) +- Docker version +- Kind cluster: Kubernetes version, node count +- cert-manager: all deployments available +- Network device: name, kind, IP/endpoint +- If `PROVIDER=cisco`: `GNMI_TARGET`, `GNMI_USER`, `GNMI_PASSWORD` confirmed and device reachable from VM +- Next step: run `/netop-test` to build and deploy the operator + +## References + +- [colima](https://github.com/abiosoft/colima) — container runtimes on macOS with minimal setup +- [kind](https://kind.sigs.k8s.io/docs/user/quick-start/) — Kubernetes in Docker +- [kubectl](https://kubernetes.io/docs/reference/kubectl/) — Kubernetes CLI reference +- [cert-manager](https://cert-manager.io/docs/) — X.509 certificate management for Kubernetes +- [containerlab](https://containerlab.dev/cmd/) — network topology emulation (CLI reference) +- [Nokia SRL containerlab kind](https://containerlab.dev/manual/kinds/nokia_srlinux/) — Nokia SR Linux node configuration +- [gnmic](https://gnmic.openconfig.net) — gNMI CLI client diff --git a/.claude/skills/netop-test/SKILL.md b/.claude/skills/netop-test/SKILL.md new file mode 100644 index 000000000..20daf0d68 --- /dev/null +++ b/.claude/skills/netop-test/SKILL.md @@ -0,0 +1,279 @@ +--- +name: netop-test +description: Build and deploy the network-operator, apply custom resources, and validate configuration via gnmic. Use after /netop-setup to run the dev/test loop against a real containerlab device. Also handles kind cluster and VM cleanup. Say "no vm" or "local" to run commands on the host machine instead. +argument-hint: [ [expected-result.json] | no-vm | local] +allowed-tools: [Bash, Read, Write, AskUserQuestion] +--- + +# netop-test + +Runs the network-operator dev/test loop: +1. Build Docker image and load into kind +2. Deploy (or redeploy) the operator +3. Apply custom resources +4. Validate with gnmic +5. Test report +6. Cleanup (optional) + +Prerequisites: `/netop-setup` has been run — VM, kind cluster, and containerlab device are all running. + +> **No-VM shortcut:** pass `no-vm`, `local`, or any similar phrasing to run all commands directly on the host machine instead of inside the VM. + +## Arguments + +The user can pass optional arguments via `$ARGUMENTS`: + +- **CR file** (`@my-sample.yaml`) — a custom CR YAML to apply instead of picking from `config/samples/`. Read the file, apply it directly. +- **Expected result** (`@my-expected-result.json`) — a JSON file with the expected gnmic state after reconciliation. Use it to compare against the actual `gnmic get` response in Step 4. +- **`no-vm` / `local`** — run all commands on the host machine instead of inside the VM. + +Examples: +``` +/netop-test +/netop-test no-vm +/netop-test @config/samples/v1alpha1_banner.yaml +/netop-test @config/samples/v1alpha1_banner.yaml @test/gnmi/testdata/openconfig/banner.txt +``` + +If an expected result file is provided, use it as the ground truth in Step 5 instead of inferring the expected value from the CR spec. + +### Parsing testdata files (`test/gnmi/testdata/`) + +If the user passes a file from `test/gnmi/testdata/` (e.g. `@test/gnmi/testdata/openconfig/banner.txt`), parse it as follows: + +``` +# +-- / -- + +-- state -- + +``` + +- Everything between `-- / --` and `-- state --` is the CR YAML → apply it in Step 3 +- Everything after `-- state --` is the expected gnmic state JSON → use it as the expected value in Step 5 +- The `deviceRef.name` in the CR YAML refers to `device` by default — replace it with the actual device name (`leaf1`) before applying + +Example (`banner.txt`): +``` +# Banner PreLogin +-- banners/banner -- +apiVersion: networking.metal.ironcore.dev/v1alpha1 +kind: Banner +... +-- state -- +{ + "openconfig-system:system": { + "config": { + "login-banner": "Unauthorized access is prohibited." + } + } +} +``` + +## Environment + +``` +VM_EXEC="colima exec -p network-operator --" # or: multipass exec network-operator -- + # or: empty ("") to run locally +``` + +All commands below are shown as bare commands. Wrap them with `$VM_EXEC bash -c ""` when running in the VM, or run them directly on the host when `no-vm` / `local` is passed. + +> **Why no `LOCALBIN` prefix?** `LOCALBIN` is set in the VM's `~/.bashrc` during `/netop-setup` — all `make` calls pick it up automatically, and tools like `kind` and `kustomize` are on `PATH`. When running locally, the Makefile default (`./bin`) applies. + +## Step 1: Build & load image + +Build the operator image: +```bash +make docker-build IMG=ghcr.io/ironcore-dev/network-operator:latest +``` + +Load the image into the kind cluster: +```bash +kind load docker-image ghcr.io/ironcore-dev/network-operator:latest --name network-operator +``` + +## Step 2: Deploy the operator + +Ask the user: **fresh deploy or redeploy?** + +**Fresh deploy** — operator not yet running in the cluster: + +Ensure kustomize is installed and set the image on the fly (without modifying tracked files): +```bash +make kustomize +cd config/develop && kustomize edit set image controller=ghcr.io/ironcore-dev/network-operator:latest && kustomize build . | kubectl apply -f - && git checkout kustomization.yaml +``` + +> `git checkout kustomization.yaml` reverts the image edit so the file stays clean. +> `config/develop/manager_patch.yaml` sets `--provider=openconfig` — read it first to confirm the provider is correct for the current session. + +**Redeploy** — operator already running, restart with the new image: +```bash +kubectl rollout restart deployment/network-operator-controller-manager -n network-operator-system +kubectl rollout status deployment/network-operator-controller-manager -n network-operator-system --timeout=60s +``` + +Check manager logs for startup errors: +```bash +kubectl logs -n network-operator-system -l control-plane=controller-manager --tail=30 +``` + +## Step 3: Apply custom resources + +Ask the user: **use samples from `config/samples/` or provide a custom YAML path?** + +The `Device` resource must be applied first — other resources depend on it. Copy to a temp file, patch address and credentials for the Nokia SRL device, then apply: +```bash +cp config/samples/v1alpha1_device.yaml /tmp/device.yaml +sed -i 's|address: .*|address: 172.20.20.2:57400|' /tmp/device.yaml +sed -i 's|password: .*|password: NokiaSrl1!|' /tmp/device.yaml +kubectl apply -f /tmp/device.yaml +``` + +If the user is using a different device, ask for the correct address and credentials before patching. + +Verify the device is reconciled: +```bash +kubectl get device -A +``` + +Then apply additional resources: +```bash +kubectl apply -f config/samples/.yaml +``` + +After applying any CR, check reconciliation status: +```bash +kubectl get -A +``` + +Look for `READY=True`. If not ready, check operator logs: +```bash +kubectl logs -n network-operator-system -l control-plane=controller-manager --tail=50 +``` + +### Generic CRD test pattern + +For any CR you want to test: + +1. **Find the sample** in `config/samples/` (e.g. `v1alpha1_banner.yaml`) +2. **Check the CR references the correct device** — `deviceRef.name: leaf1` or label `networking.metal.ironcore.dev/device-name: leaf1` +3. **Apply it:** `kubectl apply -f config/samples/v1alpha1_.yaml` +4. **Verify reconciliation:** `kubectl get -A` — expect `READY=True` +5. **Find the gNMI path** — open `internal/provider/openconfig/.go` and look for the `XPath()` method + - Banner (PreLogin): `openconfig-system:system/config/login-banner` + - DNS: `openconfig-system:system/dns` +6. **Validate with gnmic** (see Step 4) + +## Step 4: Validate with gnmic + +For each CR tested, run a gnmic get using the XPath from the provider source: +```bash +gnmic -a 172.20.20.2 --port 57400 -u admin -p 'NokiaSrl1!' --skip-verify --encoding JSON_IETF get --path '' +``` + +Ask the user if they want to query a different path or device. Substitute accordingly. + +### Optional: Show device capabilities + +If the user asks to see what the device supports, run a gnmic capabilities request: +```bash +gnmic -a 172.20.20.2 --port 57400 -u admin -p 'NokiaSrl1!' --skip-verify capabilities +``` + +This returns the supported YANG models, encodings, and gNMI version — useful for confirming which OpenConfig paths are available on the device before testing. + +### Optional: Query device configuration + +If the user asks to see a specific part of the device configuration, run a gnmic get with the path they provide: +```bash +gnmic -a 172.20.20.2 --port 57400 -u admin -p 'NokiaSrl1!' --skip-verify --encoding JSON_IETF get --path '' +``` + +Examples the user might ask: +- `show me device configuration openconfig-system:system/dns` +- `show me device configuration openconfig-interfaces:interfaces` +- `get openconfig-system:system/config/login-banner` + +Always print the full JSON response without truncation. + +If the user asks to see the running lab topology: +```bash +containerlab inspect -a +``` + +This lists all running containerlab labs, node names, kinds, images, states, and management IP addresses. + +## Step 5: Test report + +After all CRs have been applied and validated, print a structured test report. + +For each CR tested show: +1. **Applied YAML:** `kubectl get -n -o yaml` +2. **gnmic validation:** exact command and full JSON response (always shown regardless of whether an expected file was provided) +3. **Operator logs:** `kubectl logs -n network-operator-system -l control-plane=controller-manager --tail=100 | grep -i '\|error\|warn'` + +**Validation logic:** +- **Expected file provided** (`-- state --` section or JSON file) → compare gnmic response against it field by field +- **No expected file** → infer expected values from the CR spec fields (e.g. `spec.message.inline` for Banner) and validate those fields in the response +- **Always** print the full raw gnmic JSON response regardless — never truncate it + +End with a box-drawing summary table: + +``` + Test Report + + ┌──────────────┬──────────────┬───────────┬───────┬─────────────────────────────────────────┬─────────────────┐ + │ CR Name │ Kind │ Namespace │ Ready │ gNMI Path │ Result │ + ├──────────────┼──────────────┼───────────┼───────┼─────────────────────────────────────────┼─────────────────┤ + │ banner │ Banner │ default │ True │ openconfig-system:system/config/... │ ✓ value matches │ + └──────────────┴──────────────┴───────────┴───────┴─────────────────────────────────────────┴─────────────────┘ +``` + +If a CR maps to multiple gNMI paths (e.g. ManagementAccess has gRPC and SSH), add one row per path. + +Mark result as: +- `✓ value matches` — gnmic response matches expected/inferred value +- `✗ mismatch` — differs (show diff inline below table) +- `✗ not found` — gnmic returned empty or error + +Below the table, always show the full gnmic JSON response for each row: +``` + gnmic get openconfig-system:system/config/login-banner + ────────────────────────────────────────────────────── + { + "openconfig-system:system": { + "config": { + "login-banner": "###################################################\n# WARNING: ..." + } + } + } +``` + +## Step 6: Cleanup + +**Always ask before any destructive action.** + +Delete the kind cluster: +```bash +kind delete cluster --name network-operator +``` + +Stop the VM (keeps data): +```bash +colima stop --profile network-operator +``` + +Delete the VM (destroys all data — only if user explicitly confirms): +```bash +colima delete -p network-operator +``` + +## References + +- [kustomize](https://kubectl.docs.kubernetes.io/references/kustomize/) — Kubernetes configuration management +- [kind](https://kind.sigs.k8s.io/docs/user/quick-start/) — Kubernetes in Docker +- [kubectl](https://kubernetes.io/docs/reference/kubectl/) — Kubernetes CLI reference +- [gnmic](https://gnmic.openconfig.net) — gNMI CLI client +- [OpenConfig YANG doc](https://openconfig.net/projects/models/schemadocs/) — OpenConfig YANG schemas diff --git a/README.md b/README.md index 7f360df88..66f79f762 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,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). From 58863bb88ce1802b9414e098f584ca6545f45c01 Mon Sep 17 00:00:00 2001 From: Robert Gildein Date: Mon, 7 Sep 2026 17:15:10 +0200 Subject: [PATCH 2/2] Replace 3 skills with only two. One skills used for setting containerlab envirenment and another for running all local tests + test against real device. Signed-off-by: Robert Gildein --- .claude/skills/containerlab/SKILL.md | 220 ++++++++++++++++++ .claude/skills/netop-check/SKILL.md | 105 --------- .claude/skills/netop-setup/SKILL.md | 249 -------------------- .claude/skills/netop-test/SKILL.md | 279 ----------------------- .claude/skills/netop/SKILL.md | 325 +++++++++++++++++++++++++++ Makefile | 8 + config/develop/manager_patch.yaml | 5 +- 7 files changed, 557 insertions(+), 634 deletions(-) create mode 100644 .claude/skills/containerlab/SKILL.md delete mode 100644 .claude/skills/netop-check/SKILL.md delete mode 100644 .claude/skills/netop-setup/SKILL.md delete mode 100644 .claude/skills/netop-test/SKILL.md create mode 100644 .claude/skills/netop/SKILL.md 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-check/SKILL.md b/.claude/skills/netop-check/SKILL.md deleted file mode 100644 index 8f563d7dc..000000000 --- a/.claude/skills/netop-check/SKILL.md +++ /dev/null @@ -1,105 +0,0 @@ ---- -name: netop-check -description: Run local development checks for network-operator — lint, unit tests, and gNMI integration tests. Use before committing or opening a PR. All commands run on the host machine (no VM needed). -argument-hint: [lint | test | all] -allowed-tools: [Bash, Read, AskUserQuestion] ---- - -# netop-check - -Runs local development checks: -1. Vet (go vet — fast static analysis) -2. Lint (golangci-lint) -3. Unit tests -4. gNMI functional tests - -All commands run directly on the host machine in the repo root. - -## Current changes - -```bash -git diff HEAD -``` - -## Instructions - -Run the phases below in order, or just the one the user asked for via `$ARGUMENTS`. - -### Step 1: Vet - -```bash -make vet -``` - -`go vet` catches real bugs — incorrect format strings, unreachable code, suspicious struct tags, etc. It's fast and should always pass. - -If vet **fails** → show the errors and stop. These are likely bugs that need manual fixes before proceeding. - -### Step 2: Lint - -```bash -make lint -``` - -If lint **passes** → report success and continue. - -If lint **fails** → show the errors and ask the user which fix to try: - -- `make fmt` — fixes import ordering and formatting (goimports + gofumpt), style only -- `make lint-fix` — runs golangci-lint with `--fix`, auto-fixes some lint issues beyond formatting -- Both — run `make fmt` first, then `make lint-fix` - -Then re-run `make lint` to confirm the remaining errors (if any) need manual fixes. - -> **Note:** Neither command resolves logic or type errors — those need manual fixes. - -### Step 3: Unit tests - -```bash -make test -``` - -This runs all tests excluding `/e2e` and `/lab` subdirectories and produces `cover.out`. - -If tests fail → show the failing test names and error output. - -### Step 4: gNMI integration tests - -```bash -make test-gnmi -``` - -This builds and runs the fake gNMI server from `test/gnmi/` and executes the integration tests against it. Fully standalone — no kind cluster or VM needed. - -If tests fail → show the failing testdata files and the diff between expected and actual state. - -### Summary - -After all phases complete, print a report: - -``` - Local Dev Report - ──────────────────────────────────────────────────────── - Vet: ✓ passed (or ✗ N issues — list them) - Lint: ✓ passed (or ✗ N issues — list them) - (fmt offered: yes/no) - Unit tests: ✓ N passed, 0 failed (or ✗ N failed — list failing tests) - gNMI tests: ✓ N passed, 0 failed (or ✗ N failed — list failing testdata files) - ──────────────────────────────────────────────────────── - Overall: ✓ all checks passed (or ✗ see above) -``` - -For gNMI test failures, show the diff between expected and actual state: -``` - FAIL: testdata/openconfig/banner.txt - Expected: {"openconfig-system:system":{"config":{"login-banner":"..."}}} - Actual: {} -``` - -## References - -- [go vet](https://pkg.go.dev/cmd/vet) — static analysis tool built into Go -- [golangci-lint](https://golangci-lint.run) — aggregated linter runner (custom build used here via `.custom-gcl.yaml`) -- [goimports](https://pkg.go.dev/golang.org/x/tools/cmd/goimports) — fixes import grouping and formatting (`make fmt`) -- [gofumpt](https://github.com/mvdan/gofumpt) — stricter gofmt, run alongside goimports (`make fmt`) -- [gnmic](https://gnmic.openconfig.net) — gNMI CLI client used for validation diff --git a/.claude/skills/netop-setup/SKILL.md b/.claude/skills/netop-setup/SKILL.md deleted file mode 100644 index 3b12cf878..000000000 --- a/.claude/skills/netop-setup/SKILL.md +++ /dev/null @@ -1,249 +0,0 @@ ---- -name: netop-setup -description: One-time setup of the network-operator test environment. Provisions a colima VM, creates a kind cluster with cert-manager, and deploys a containerlab network device. Use this before the first test session or after a full teardown. Say "no vm" or "skip vm" to skip the VM provisioning step. -argument-hint: [no-vm | skip-vm] -allowed-tools: [Bash, Read, Write, AskUserQuestion] ---- - -# netop-setup - -Sets up the full test environment for network-operator from scratch: -1. Setup VM (profile: `network-operator`) -2. Install tools in VM -3. Kind cluster + cert-manager -4. Containerlab network device - -> **No-VM shortcut:** pass `no-vm`, `skip vm`, `without vm`, or `run without VM` to skip Steps 1 and 2. - -## Environment - -At the start of the session, ask the user which provider they plan to test (if not already known from `$ARGUMENTS`): -- **openconfig / Nokia SRL** → `PROVIDER=openconfig` -- **cisco** → `PROVIDER=cisco` - -Also ask which VM tool they are using (default: colima): -- **colima** → `VM_EXEC="colima exec -p network-operator --"` -- **multipass** → `VM_EXEC="multipass exec network-operator --"` - -These variables are used in every command below: - -``` -PROVIDER=openconfig # or cisco -VM_EXEC="colima exec -p network-operator --" # or multipass exec network-operator -- -``` - -`LOCALBIN` is set persistently in the VM's `~/.bashrc` during Step 2 — no need to prefix it on any `make` command. - -> **No-VM case:** `LOCALBIN` is not set. The Makefile default (`./bin`) applies automatically. - -The VM wrapper for all Step 2+ commands is: -```bash -$VM_EXEC bash -c "" -``` - -> The host home directory is mounted at the same path inside the VM — commands run from the same directory as on the host, so no `cd` is needed. - -> All commands in Steps 2, 3, and 4 use this wrapper. It is not repeated in each step — just substitute `` with the bare command shown. - -## Step 1: Setup VM - -> **Skip Steps 1 and 2** if the user passes `no-vm` or any similar phrasing. - -Check the state of the `network-operator` colima profile: - -```bash -colima list -``` - -- **Running** → check specs match defaults (4 CPU, 8 GB, 60 GB disk). If they differ, warn the user and ask if they want to recreate: - ```bash - colima delete -p network-operator - # then create as below - ``` -- **Stopped** → start it: - ```bash - colima start --profile network-operator - ``` -- **Not listed** → create it: - ```bash - colima start --cpu 4 --memory 8 --disk 60 --network-address --profile network-operator - ``` - -Verify after start: -```bash -colima list -``` - -Ensure `~/.local/bin` exists, is on PATH, and `LOCALBIN` is exported in the VM: - -```bash -mkdir -p ~/.local/bin -grep -qxF 'export LOCALBIN="$HOME/.local/bin"' ~/.bashrc || echo 'export LOCALBIN="$HOME/.local/bin"' >> ~/.bashrc -grep -qxF 'export PATH="$LOCALBIN:$PATH"' ~/.bashrc || echo 'export PATH="$LOCALBIN:$PATH"' >> ~/.bashrc -export LOCALBIN="$HOME/.local/bin" -export PATH="$LOCALBIN:$PATH" -``` - -## Step 2: Install tools in VM - -> **Skip this step** if the user is running without a VM. - -Install required tools if not already present: - -```bash -sudo apt-get update -qq -sudo apt-get install -y make curl jq vim snapd -which yq || sudo snap install yq -which go || sudo snap install go --classic -which kubectl || sudo snap install kubectl --classic -which k || sudo snap alias kubectl k -which gnmic || bash -c "$(curl -sL https://get-gnmic.openconfig.net)" -``` - -## Step 3: Kind cluster + cert-manager - -```bash -make kind -make kind-create -``` - -Wait for node ready: -```bash -kubectl wait --for=condition=Ready node --all --timeout=120s -``` - -Install cert-manager: -```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 -``` - -Verify: -```bash -kubectl get nodes -kubectl get pods -n cert-manager -``` - -## Step 4: Containerlab device - -Ask the user which device type to use: - -**Option A — Nokia SRL (default, arm64-compatible)** -**Option B — Remote Cisco device (team cloud via SSH port forwarding)** - -### Option A: Nokia SRL - -Check if containerlab is installed: -```bash -containerlab version 2>/dev/null || bash -c "$(curl -sL https://get.containerlab.dev)" -``` - -Write the topology file to `/tmp/srl01.clab.yml`: - -```yaml -name: srlceos01 - -topology: - nodes: - srl: - kind: nokia_srlinux - image: ghcr.io/nokia/srlinux:26.7.1 - startup-config: |- - system name host-name srl - system grpc-server mgmt yang-models openconfig - ports: - - 57022:22 - - 57400:57400 - - links: - - endpoints: ["srl:ethernet-1/1", "srl:ethernet-1/2"] -``` - -Deploy: -```bash -containerlab deploy -d -t /tmp/srl01.clab.yml -``` - -If the container already exists or the user wants to reconfigure: -```bash -containerlab deploy -d --reconfigure -t /tmp/srl01.clab.yml -``` - -Wait until running: -```bash -docker inspect -f '{{.State.Status}}' clab-srlceos01-srl -``` - -Show device IP: -```bash -containerlab inspect -t /tmp/srl01.clab.yml -``` - -The Nokia SRL management IP is typically `172.20.20.2` — confirm and note it for `/netop-test`. - -### Option B: Remote Cisco device - -> **Note:** Local Cisco N9Kv deployment is not possible on Apple Silicon — nested virtualization required for QEMU x86 emulation is not supported. Use a remote device instead (direct access or via SSH port forwarding — that's the user's responsibility). - -Ask the user for the device connection details: -- `CISCO_IP` — IP address reachable from the VM (e.g. `10.0.0.5` or `127.0.0.1` if port-forwarded) -- `CISCO_PORT` — gNMI port (default: `57400`) -- `CISCO_USER` — gNMI username (default: `admin`) -- `CISCO_PASSWORD` — gNMI password - -Verify connectivity from inside the VM: -```bash -nc -z $CISCO_IP $CISCO_PORT && echo "device reachable" || echo "device not reachable — check IP, port, and any required port forwarding" -``` - -> **Localhost warning:** If the user provides `127.0.0.1` or `localhost` as `CISCO_IP`, warn them that this refers to the VM itself, not the Mac host. Detect the Mac host IP as seen from the VM (its default gateway) and use that instead: -> ```bash -> HOST_IP=$(ip route | awk '/default/ {print $3}') -> echo "Use $HOST_IP instead of 127.0.0.1" -> ``` -> Update `CISCO_IP` to `$HOST_IP` before proceeding. - -Note these values for `/netop-test`: -``` -GNMI_TARGET=$CISCO_IP:$CISCO_PORT -GNMI_USER=$CISCO_USER -GNMI_PASSWORD=$CISCO_PASSWORD -``` - -## Summary - -Run the following to show the full state of the dev environment: - -```bash -# Docker version -docker --version - -# Kubernetes cluster version and nodes -kubectl version -kubectl get nodes - -# All pods (wait until ready) -kubectl wait --for=condition=Ready pod --all -A --timeout=120s && kubectl get pods -A - -# Containerlab device status -containerlab inspect -a -``` - -Print a final summary: -- Colima profile: `network-operator` (CPU, memory, disk) -- Docker version -- Kind cluster: Kubernetes version, node count -- cert-manager: all deployments available -- Network device: name, kind, IP/endpoint -- If `PROVIDER=cisco`: `GNMI_TARGET`, `GNMI_USER`, `GNMI_PASSWORD` confirmed and device reachable from VM -- Next step: run `/netop-test` to build and deploy the operator - -## References - -- [colima](https://github.com/abiosoft/colima) — container runtimes on macOS with minimal setup -- [kind](https://kind.sigs.k8s.io/docs/user/quick-start/) — Kubernetes in Docker -- [kubectl](https://kubernetes.io/docs/reference/kubectl/) — Kubernetes CLI reference -- [cert-manager](https://cert-manager.io/docs/) — X.509 certificate management for Kubernetes -- [containerlab](https://containerlab.dev/cmd/) — network topology emulation (CLI reference) -- [Nokia SRL containerlab kind](https://containerlab.dev/manual/kinds/nokia_srlinux/) — Nokia SR Linux node configuration -- [gnmic](https://gnmic.openconfig.net) — gNMI CLI client diff --git a/.claude/skills/netop-test/SKILL.md b/.claude/skills/netop-test/SKILL.md deleted file mode 100644 index 20daf0d68..000000000 --- a/.claude/skills/netop-test/SKILL.md +++ /dev/null @@ -1,279 +0,0 @@ ---- -name: netop-test -description: Build and deploy the network-operator, apply custom resources, and validate configuration via gnmic. Use after /netop-setup to run the dev/test loop against a real containerlab device. Also handles kind cluster and VM cleanup. Say "no vm" or "local" to run commands on the host machine instead. -argument-hint: [ [expected-result.json] | no-vm | local] -allowed-tools: [Bash, Read, Write, AskUserQuestion] ---- - -# netop-test - -Runs the network-operator dev/test loop: -1. Build Docker image and load into kind -2. Deploy (or redeploy) the operator -3. Apply custom resources -4. Validate with gnmic -5. Test report -6. Cleanup (optional) - -Prerequisites: `/netop-setup` has been run — VM, kind cluster, and containerlab device are all running. - -> **No-VM shortcut:** pass `no-vm`, `local`, or any similar phrasing to run all commands directly on the host machine instead of inside the VM. - -## Arguments - -The user can pass optional arguments via `$ARGUMENTS`: - -- **CR file** (`@my-sample.yaml`) — a custom CR YAML to apply instead of picking from `config/samples/`. Read the file, apply it directly. -- **Expected result** (`@my-expected-result.json`) — a JSON file with the expected gnmic state after reconciliation. Use it to compare against the actual `gnmic get` response in Step 4. -- **`no-vm` / `local`** — run all commands on the host machine instead of inside the VM. - -Examples: -``` -/netop-test -/netop-test no-vm -/netop-test @config/samples/v1alpha1_banner.yaml -/netop-test @config/samples/v1alpha1_banner.yaml @test/gnmi/testdata/openconfig/banner.txt -``` - -If an expected result file is provided, use it as the ground truth in Step 5 instead of inferring the expected value from the CR spec. - -### Parsing testdata files (`test/gnmi/testdata/`) - -If the user passes a file from `test/gnmi/testdata/` (e.g. `@test/gnmi/testdata/openconfig/banner.txt`), parse it as follows: - -``` -# --- / -- - --- state -- - -``` - -- Everything between `-- / --` and `-- state --` is the CR YAML → apply it in Step 3 -- Everything after `-- state --` is the expected gnmic state JSON → use it as the expected value in Step 5 -- The `deviceRef.name` in the CR YAML refers to `device` by default — replace it with the actual device name (`leaf1`) before applying - -Example (`banner.txt`): -``` -# Banner PreLogin --- banners/banner -- -apiVersion: networking.metal.ironcore.dev/v1alpha1 -kind: Banner -... --- state -- -{ - "openconfig-system:system": { - "config": { - "login-banner": "Unauthorized access is prohibited." - } - } -} -``` - -## Environment - -``` -VM_EXEC="colima exec -p network-operator --" # or: multipass exec network-operator -- - # or: empty ("") to run locally -``` - -All commands below are shown as bare commands. Wrap them with `$VM_EXEC bash -c ""` when running in the VM, or run them directly on the host when `no-vm` / `local` is passed. - -> **Why no `LOCALBIN` prefix?** `LOCALBIN` is set in the VM's `~/.bashrc` during `/netop-setup` — all `make` calls pick it up automatically, and tools like `kind` and `kustomize` are on `PATH`. When running locally, the Makefile default (`./bin`) applies. - -## Step 1: Build & load image - -Build the operator image: -```bash -make docker-build IMG=ghcr.io/ironcore-dev/network-operator:latest -``` - -Load the image into the kind cluster: -```bash -kind load docker-image ghcr.io/ironcore-dev/network-operator:latest --name network-operator -``` - -## Step 2: Deploy the operator - -Ask the user: **fresh deploy or redeploy?** - -**Fresh deploy** — operator not yet running in the cluster: - -Ensure kustomize is installed and set the image on the fly (without modifying tracked files): -```bash -make kustomize -cd config/develop && kustomize edit set image controller=ghcr.io/ironcore-dev/network-operator:latest && kustomize build . | kubectl apply -f - && git checkout kustomization.yaml -``` - -> `git checkout kustomization.yaml` reverts the image edit so the file stays clean. -> `config/develop/manager_patch.yaml` sets `--provider=openconfig` — read it first to confirm the provider is correct for the current session. - -**Redeploy** — operator already running, restart with the new image: -```bash -kubectl rollout restart deployment/network-operator-controller-manager -n network-operator-system -kubectl rollout status deployment/network-operator-controller-manager -n network-operator-system --timeout=60s -``` - -Check manager logs for startup errors: -```bash -kubectl logs -n network-operator-system -l control-plane=controller-manager --tail=30 -``` - -## Step 3: Apply custom resources - -Ask the user: **use samples from `config/samples/` or provide a custom YAML path?** - -The `Device` resource must be applied first — other resources depend on it. Copy to a temp file, patch address and credentials for the Nokia SRL device, then apply: -```bash -cp config/samples/v1alpha1_device.yaml /tmp/device.yaml -sed -i 's|address: .*|address: 172.20.20.2:57400|' /tmp/device.yaml -sed -i 's|password: .*|password: NokiaSrl1!|' /tmp/device.yaml -kubectl apply -f /tmp/device.yaml -``` - -If the user is using a different device, ask for the correct address and credentials before patching. - -Verify the device is reconciled: -```bash -kubectl get device -A -``` - -Then apply additional resources: -```bash -kubectl apply -f config/samples/.yaml -``` - -After applying any CR, check reconciliation status: -```bash -kubectl get -A -``` - -Look for `READY=True`. If not ready, check operator logs: -```bash -kubectl logs -n network-operator-system -l control-plane=controller-manager --tail=50 -``` - -### Generic CRD test pattern - -For any CR you want to test: - -1. **Find the sample** in `config/samples/` (e.g. `v1alpha1_banner.yaml`) -2. **Check the CR references the correct device** — `deviceRef.name: leaf1` or label `networking.metal.ironcore.dev/device-name: leaf1` -3. **Apply it:** `kubectl apply -f config/samples/v1alpha1_.yaml` -4. **Verify reconciliation:** `kubectl get -A` — expect `READY=True` -5. **Find the gNMI path** — open `internal/provider/openconfig/.go` and look for the `XPath()` method - - Banner (PreLogin): `openconfig-system:system/config/login-banner` - - DNS: `openconfig-system:system/dns` -6. **Validate with gnmic** (see Step 4) - -## Step 4: Validate with gnmic - -For each CR tested, run a gnmic get using the XPath from the provider source: -```bash -gnmic -a 172.20.20.2 --port 57400 -u admin -p 'NokiaSrl1!' --skip-verify --encoding JSON_IETF get --path '' -``` - -Ask the user if they want to query a different path or device. Substitute accordingly. - -### Optional: Show device capabilities - -If the user asks to see what the device supports, run a gnmic capabilities request: -```bash -gnmic -a 172.20.20.2 --port 57400 -u admin -p 'NokiaSrl1!' --skip-verify capabilities -``` - -This returns the supported YANG models, encodings, and gNMI version — useful for confirming which OpenConfig paths are available on the device before testing. - -### Optional: Query device configuration - -If the user asks to see a specific part of the device configuration, run a gnmic get with the path they provide: -```bash -gnmic -a 172.20.20.2 --port 57400 -u admin -p 'NokiaSrl1!' --skip-verify --encoding JSON_IETF get --path '' -``` - -Examples the user might ask: -- `show me device configuration openconfig-system:system/dns` -- `show me device configuration openconfig-interfaces:interfaces` -- `get openconfig-system:system/config/login-banner` - -Always print the full JSON response without truncation. - -If the user asks to see the running lab topology: -```bash -containerlab inspect -a -``` - -This lists all running containerlab labs, node names, kinds, images, states, and management IP addresses. - -## Step 5: Test report - -After all CRs have been applied and validated, print a structured test report. - -For each CR tested show: -1. **Applied YAML:** `kubectl get -n -o yaml` -2. **gnmic validation:** exact command and full JSON response (always shown regardless of whether an expected file was provided) -3. **Operator logs:** `kubectl logs -n network-operator-system -l control-plane=controller-manager --tail=100 | grep -i '\|error\|warn'` - -**Validation logic:** -- **Expected file provided** (`-- state --` section or JSON file) → compare gnmic response against it field by field -- **No expected file** → infer expected values from the CR spec fields (e.g. `spec.message.inline` for Banner) and validate those fields in the response -- **Always** print the full raw gnmic JSON response regardless — never truncate it - -End with a box-drawing summary table: - -``` - Test Report - - ┌──────────────┬──────────────┬───────────┬───────┬─────────────────────────────────────────┬─────────────────┐ - │ CR Name │ Kind │ Namespace │ Ready │ gNMI Path │ Result │ - ├──────────────┼──────────────┼───────────┼───────┼─────────────────────────────────────────┼─────────────────┤ - │ banner │ Banner │ default │ True │ openconfig-system:system/config/... │ ✓ value matches │ - └──────────────┴──────────────┴───────────┴───────┴─────────────────────────────────────────┴─────────────────┘ -``` - -If a CR maps to multiple gNMI paths (e.g. ManagementAccess has gRPC and SSH), add one row per path. - -Mark result as: -- `✓ value matches` — gnmic response matches expected/inferred value -- `✗ mismatch` — differs (show diff inline below table) -- `✗ not found` — gnmic returned empty or error - -Below the table, always show the full gnmic JSON response for each row: -``` - gnmic get openconfig-system:system/config/login-banner - ────────────────────────────────────────────────────── - { - "openconfig-system:system": { - "config": { - "login-banner": "###################################################\n# WARNING: ..." - } - } - } -``` - -## Step 6: Cleanup - -**Always ask before any destructive action.** - -Delete the kind cluster: -```bash -kind delete cluster --name network-operator -``` - -Stop the VM (keeps data): -```bash -colima stop --profile network-operator -``` - -Delete the VM (destroys all data — only if user explicitly confirms): -```bash -colima delete -p network-operator -``` - -## References - -- [kustomize](https://kubectl.docs.kubernetes.io/references/kustomize/) — Kubernetes configuration management -- [kind](https://kind.sigs.k8s.io/docs/user/quick-start/) — Kubernetes in Docker -- [kubectl](https://kubernetes.io/docs/reference/kubectl/) — Kubernetes CLI reference -- [gnmic](https://gnmic.openconfig.net) — gNMI CLI client -- [OpenConfig YANG doc](https://openconfig.net/projects/models/schemadocs/) — OpenConfig YANG schemas 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 534afc3a6..2c2237c97 100644 --- a/Makefile +++ b/Makefile @@ -237,10 +237,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/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