From 8fa9e8aad92e04438bfecd9d6cf663fb207f36b5 Mon Sep 17 00:00:00 2001 From: kacerr Date: Fri, 18 Sep 2026 08:12:27 +0000 Subject: [PATCH] Housekeeping: review docs into docs/, refreshed agent guide and settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four unrelated working-tree changes, none of them touching operator behaviour: - review/*.md -> docs/reviews/2026-09-17-kvm2-provider-review/, byte for byte, so the kvm2 review sits with the rest of the docs under a name that says which review it was. - AGENTS.md: replaced the generic kubebuilder scaffold guide with what is actually true of this repo — the real component layout, the lint/test commands that work here, and the behavioural gotchas (replace-on-change hashing, the cloud-init Secret label, in-memory leases). - .claude/settings.json: the allow list still carried /Users/jan.novak paths from the macOS checkout; re-pointed at /home/novakj and collapsed the one-off entries into prefixes. - config/samples/proxy_kvm2_crawl_only_one.yaml: a one-VM cut of the crawl pool, the rest of the members left commented out. Co-Authored-By: Claude Opus 5 (1M context) --- .claude/settings.json | 276 ++++++++----- AGENTS.md | 362 ++---------------- config/samples/proxy_kvm2_crawl_only_one.yaml | 103 +++++ .../REPORT.md | 0 .../2026-09-17-kvm2-provider-review}/deep.md | 0 .../escalations.md | 0 .../2026-09-17-kvm2-provider-review}/scope.md | 0 .../2026-09-17-kvm2-provider-review}/sweep.md | 0 8 files changed, 319 insertions(+), 422 deletions(-) create mode 100644 config/samples/proxy_kvm2_crawl_only_one.yaml rename {review => docs/reviews/2026-09-17-kvm2-provider-review}/REPORT.md (100%) rename {review => docs/reviews/2026-09-17-kvm2-provider-review}/deep.md (100%) rename {review => docs/reviews/2026-09-17-kvm2-provider-review}/escalations.md (100%) rename {review => docs/reviews/2026-09-17-kvm2-provider-review}/scope.md (100%) rename {review => docs/reviews/2026-09-17-kvm2-provider-review}/sweep.md (100%) diff --git a/.claude/settings.json b/.claude/settings.json index e95a782..df269ec 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -1,113 +1,185 @@ { "permissions": { "allow": [ - "Bash(go version *)", - "Bash(go mod *)", - "Read(//Users/jan.novak/.claude/skills/go-project-bootstrap/references/**)", - "Bash(ls -la /Users/jan.novak/.claude/skills/go-project-bootstrap/assets/ /Users/jan.novak/.claude/skills/go-project-bootstrap/assets/commands/ /Users/jan.novak/.claude/skills/go-project-bootstrap/assets/agents/ 2>&1)", - "Read(//Users/jan.novak/.claude/skills/go-project-bootstrap/assets/**)", - "Read(//Users/jan.novak/.claude/skills/go-project-bootstrap/assets/agents/**)", - "Read(//Users/jan.novak/.claude/skills/go-project-bootstrap/assets/commands/**)", - "Bash(mkdir -p /Users/jan.novak/srv/go/egress-proxies-operator/docs/plans && touch /Users/jan.novak/srv/go/egress-proxies-operator/docs/plans/.gitkeep && mkdir -p /Users/jan.novak/srv/go/egress-proxies-operator/.claude/commands /Users/jan.novak/srv/go/egress-proxies-operator/.claude/agents && ls -la /Users/jan.novak/srv/go/egress-proxies-operator)", - "Bash(git status)", - "Bash(git status:*)", - "Bash(git diff:*)", - "Bash(git log:*)", - "Bash(git branch:*)", - "Bash(git add:*)", - "Bash(git checkout:*)", - "Bash(git pull:*)", - "Bash(git fetch:*)", - "Bash(date:*)", - "Read(//tmp/**)", - "Read(//private/tmp/**)", - "Bash(go build:*)", - "Bash(go test:*)", - "Bash(go vet:*)", - "Bash(go mod:*)", - "Bash(go run:*)", - "Bash(go version:*)", - "Bash(go get:*)", - "Bash(golangci-lint run:*)", - "Bash(golangci-lint --version)", - "Bash(gofumpt:*)", - "Bash(tea pr create:*)", - "Bash(curl -sL https://go.dev/dl/?mode=json)", - "Bash(find / -iname \"docs-conventions.md\" -maxdepth 6)", - "Read(//Users/jan.novak/.claude/**)", - "Read(//Users/jan.novak/.claude/skills/**)", - "Bash(mkdir -p docs/plans && date \"+%Y-%m-%d-%H%M\")", - "Bash(go install *)", - "Bash(go env *)", - "Bash(git rm *)", - "Bash(mkdir -p /Users/jan.novak/srv/go/egress-proxies-operator/docs/plans-executions)", - "Bash(cd /Users/jan.novak/srv/go/egress-proxies-operator *)", - "Bash(echo \"build: $?\")", - "Bash(echo \"vet: $?\")", - "Bash(perl -i -pe 's{^\\\\t\\\\t\\\\t\\\\t\\\\t// TODO\\\\\\(user\\\\\\): Specify other spec details if needed\\\\.\\\\n}{\\\\t\\\\t\\\\t\\\\t\\\\t// A minimal, schema-valid spec so this placeholder test survives the\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t// CEL/CRD validation added in Step 1. Rewritten wholesale in Step 4\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t// alongside the real reconciler and envtest suite.\\\\n\\\\t\\\\t\\\\t\\\\t\\\\tSpec: crawlv1alpha1.ProxySpec{\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\tMode: crawlv1alpha1.ModeExternal,\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\tEndpoint: &crawlv1alpha1.EndpointSpec{Host: \"10.0.0.1\"},\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t},\\\\n}' internal/controller/proxy_controller_test.go)", - "Bash(go tool *)", - "Bash(docker version *)", - "Bash(curl -sI --max-time 5 https://hub.docker.com)", - "Bash(kind get *)", - "Bash(curl -s --max-time 5 \"https://hub.docker.com/v2/repositories/ubuntu/squid/tags?page_size=10\")", - "Bash(python3 -c ' *)", - "Bash(curl -s --max-time 5 \"https://hub.docker.com/v2/repositories/ubuntu/squid/tags?page_size=100\")", - "Bash(go doc *)", - "Bash(go list *)", - "Bash(gofmt -w internal/provider/config.go)", - "Bash(gofmt -l .)", - "Bash(git restore *)", - "Bash(make manifests *)", - "Bash(make test *)", - "Bash(KUBEBUILDER_ASSETS=\"/Users/jan.novak/srv/go/egress-proxies-operator/bin/k8s/1.36.2-darwin-arm64\" go test -race ./internal/controller/)", - "Bash(grep -n 'func Channel' -A8 __CMDSUB_OUTPUT__/sigs.k8s.io/controller-runtime@v0.24.1/pkg/source/source.go)", - "Bash(grep -n 'type GenericEvent' __CMDSUB_OUTPUT__/sigs.k8s.io/controller-runtime@v0.24.1/pkg/event/event.go)", - "Bash(KUBEBUILDER_ASSETS=__TRACKED_VAR__/bin/k8s/1.36.2-darwin-arm64 go test -race ./...)", - "Bash(cat >> *)", - "Bash(make run-dev *)", - "Bash(kubectl get *)", - "Bash(kubectl delete *)", - "Bash(make docker-build *)", - "Bash(kind load *)", - "Bash(make deploy *)", - "Bash(kubectl -n egress-proxies-operator-system rollout status deploy/egress-proxies-operator-controller-manager --timeout=120s)", - "Bash(kubectl -n egress-proxies-operator-system rollout restart deploy/egress-proxies-operator-controller-manager)", - "Bash(git -C /Users/jan.novak/srv/go/egress-proxies-operator add docs/plans/2026-08-11-1742-gcp-provider-verbose-logging.md)", - "Bash(git -C /Users/jan.novak/srv/go/egress-proxies-operator commit -m 'Add plan: verbose V-level logging in the GCP provider *)", - "Bash(echo \"exit: $?\")", - "Bash(echo \"tests exit: $?\")", - "Bash(./bin/manager --version)", - "Bash(./bin/manager-stamped --version)", - "Bash(./bin/manager-pkg --version)", - "Bash(docker run *)", - "Bash(kubectl -n egress-proxies-operator-system get pods -o wide)", - "Bash(kubectl -n egress-proxies-operator-system get deploy egress-proxies-operator-controller-manager -o jsonpath='{.spec.template.spec.containers[0].args}')", - "Bash(kubectl -n egress-proxies-operator-system logs deploy/egress-proxies-operator-controller-manager)", - "Bash(python3 -c \"import json; d=json.load\\(open\\('docs/deploy/sa_key.json'\\)\\); print\\(d.get\\('type'\\), d.get\\('client_email'\\)\\)\")", - "Bash(tea pr *)", - "Bash(git worktree *)", - "Bash(python3 -c \"import yaml; yaml.safe_load\\(open\\('.gitea/workflows/build.yaml'\\)\\); print\\('YAML OK'\\)\")", - "Bash(ruby -ryaml -e \"YAML.load_file\\('.gitea/workflows/build.yaml'\\); puts 'YAML OK'\")", - "Bash(git -C /Users/jan.novak/srv/go/egress-proxies-operator status --short --branch)", - "Bash(git -C /Users/jan.novak/srv/go/egress-proxies-operator log --oneline -1)", - "Bash(git -C /Users/jan.novak/srv/go/egress-proxies-operator tag 0.01)", - "Bash(git -C /Users/jan.novak/srv/go/egress-proxies-operator push origin 0.01)", - "Bash(chmod +x docs/demo/run-demo.sh)", - "Bash(bash -n docs/demo/run-demo.sh)", - "Bash(git add *)", - "Bash(git commit -q -m 'Add plan: KVM/libvirt provider *)", - "Bash(go get *)", "Bash(go build *)", "Bash(go test *)", - "Bash(git commit *)" + "Bash(go vet *)", + "Bash(go run *)", + "Bash(go mod *)", + "Bash(go get *)", + "Bash(go install *)", + "Bash(go env *)", + "Bash(go tool *)", + "Bash(go doc *)", + "Bash(go list *)", + "Bash(go version *)", + "Bash(gofmt *)", + "Bash(gofumpt *)", + "Bash(golangci-lint run *)", + "Bash(golangci-lint --version)", + "Bash(./bin/manager* --version)", + "Bash(make -n *)", + "Bash(make manifests *)", + "Bash(make test *)", + "Bash(make test-integration-kvm *)", + "Bash(make run-dev *)", + "Bash(make docker-build *)", + "Bash(make deploy *)", + "Bash(make kvm-ssh-secret *)", + "Bash(git status *)", + "Bash(git diff *)", + "Bash(git log *)", + "Bash(git branch)", + "Bash(git branch -a)", + "Bash(git branch --show-current)", + "Bash(git add *)", + "Bash(git commit *)", + "Bash(git fetch *)", + "Bash(git pull *)", + "Bash(git worktree list *)", + "Bash(git config --get *)", + "Bash(git config --list *)", + "Bash(git merge-tree *)", + "Bash(git ls-remote *)", + "Bash(git -c credential.helper='!f\\(\\) { echo username=kacerr; echo \"password=$\\(tr -d \"[:space:]\" < ~/.config/gitea-token\\)\"; }; f' push origin feat/kvm-provider)", + "Bash(git -c credential.helper='!f\\(\\) { echo username=kacerr; echo \"password=$\\(tr -d \"[:space:]\" < ~/.config/gitea-token\\)\"; }; f' push -q origin feat/kvm-provider)", + "Bash(git -c credential.helper='!f\\(\\) { echo username=kacerr; echo \"password=$\\(tr -d \"[:space:]\" < ~/.config/gitea-token\\)\"; }; f' push -u origin feat/kvm-provider)", + "Bash(tea pr create *)", + "Bash(tea pr list *)", + "Bash(curl -sL https://go.dev/dl/?mode=json)", + "Bash(curl -s --max-time 10 https://gitea.home.hrajfrisbee.cz/api/v1/repos/kacerr/egress-proxies-operator/*)", + "Bash(curl -s --max-time 10 'https://gitea.home.hrajfrisbee.cz/api/v1/repos/kacerr/egress-proxies-operator/*')", + "Bash(curl -sI --max-time 10 https://gitea.home.hrajfrisbee.cz/v2/kacerr/egress-proxies-operator/*)", + "Bash(curl -s -H \"Authorization: token $\\(tr -d '[:space:]' < ~/.config/gitea-token\\)\" https://gitea.home.hrajfrisbee.cz/api/v1/repos/kacerr/egress-proxies-operator/pulls/5)", + "Bash(kubectl get *)", + "Bash(kubectl wait *)", + "Bash(kubectl kustomize *)", + "Bash(kubectl apply *)", + "Bash(kubectl run *)", + "Bash(kubectl config current-context)", + "Bash(kubectl config get-contexts *)", + "Bash(kubectl config view *)", + "Bash(kubectl -n egress-proxies-operator-system get *)", + "Bash(kubectl -n egress-proxies-operator-system logs *)", + "Bash(kubectl -n egress-proxies-operator-system rollout status *)", + "Bash(kubectl -n egress-proxies-operator-system rollout restart *)", + "Bash(kind get *)", + "Bash(kind load *)", + "Bash(kind version *)", + "Bash(docker version *)", + "Bash(docker info *)", + "Bash(docker image ls *)", + "Bash(docker image inspect *)", + "Bash(docker manifest inspect *)", + "Bash(docker history *)", + "Bash(docker save *)", + "Bash(docker run *)", + "Bash(docker exec *)", + "Bash(virsh -c qemu:///system list *)", + "Bash(virsh -c qemu:///system net-list *)", + "Bash(virsh -c qemu:///system net-dumpxml *)", + "Bash(virsh -c qemu:///system pool-list *)", + "Bash(virsh -c qemu:///system pool-dumpxml *)", + "Bash(virsh -c qemu:///system vol-list *)", + "Bash(ssh -i ~/.ssh/proxy-operator_ed25519 * novakj@beelink.tail8c69c.ts.net *)", + "Bash(ip -br *)", + "Bash(getent hosts *)", + "Bash(tailscale status *)", + "Bash(python3 *)", + "Bash(grep *)", + "Bash(echo \"*$?\")", + "Bash(date *)", + "Bash(env)", + "Bash(command -v *)", + "Bash(cat >> *)", + "Bash(tar -tf *)", + "Bash(bash -n *)", + "Bash(mkdir -p docs/*)", + "Read(//home/novakj/**)", + "Read(//Users/jan.novak/.claude/**)", + "Read(//tmp/**)", + "Read(//private/tmp/**)", + "Read(//etc/**)", + "Read(//dev/**)", + "Read(//run/libvirt/**)", + "WebSearch", + "WebFetch(domain:gitlab.com)", + "WebFetch(domain:bugs.debian.org)", + "WebFetch(domain:tracker.debian.org)", + "WebFetch(domain:cloud.debian.org)", + "WebFetch(domain:alioth-lists.debian.net)", + "WebFetch(domain:www.mail-archive.com)", + "WebFetch(domain:forum.proxmox.com)", + "Bash(curl -s --max-time 10 'https://gitea.home.hrajfrisbee.cz/api/v1/repos/kacerr/egress-proxies-operator/pulls?state=open')", + "Bash(python3 -c 'import json,sys; [print\\(p[\"number\"], p[\"head\"][\"ref\"], \"->\", p[\"base\"][\"ref\"], \"|\", p[\"title\"]\\) for p in json.load\\(sys.stdin\\)]')", + "Bash(curl -s --max-time 10 'https://gitea.home.hrajfrisbee.cz/api/v1/repos/kacerr/egress-proxies-operator/pulls/5')", + "Bash(python3 -c 'import json,sys; p=json.load\\(sys.stdin\\); print\\(p[\"state\"], \"merged=\", p[\"merged\"], p.get\\(\"merge_commit_sha\"\\)\\)')", + "Read(//home/novakj/srv/egress-proxies-operator-simplify/origin/**)", + "Bash(echo \"exit=$?\")", + "Read(//home/novakj/srv/egress-proxies-operator-simplify/**)", + "Bash(git -C /home/novakj/srv/egress-proxies-operator-simplify diff 82eeeef -- internal/controller/proxy_controller.go internal/provider/kvm/errors.go)", + "Bash(git switch *)", + "Bash(git -C /home/novakj/srv/egress-proxies-operator-simplify add -A)", + "Bash(git -C /home/novakj/srv/egress-proxies-operator-simplify commit -q -m 'WIP: uncommitted simplify-pass edits preserved before worktree removal *)", + "Bash(git -C /home/novakj/srv/egress-proxies-operator-simplify log --oneline -1)", + "Bash(git worktree *)", + "Bash(git branch *)", + "Bash(git stash *)", + "Bash(make generate *)", + "Bash(bin/kustomize build *)", + "Bash(grep -Ev \"^\\\\s*$\")", + "Bash(make lint *)", + "Bash(command -v kind docker)", + "Bash(bin/golangci-lint version *)", + "Bash(make test-e2e *)", + "Bash(echo \"make test-e2e exit=$?\")", + "Bash(ls \"$\\(go env GOMODCACHE\\)/golang.org/\")", + "Bash(ls \"$\\(go env GOMODCACHE\\)/golang.org/toolchain@\"* -d)", + "Bash(ps -eo pid,ppid,etime,args)", + "Bash(kubectl config *)", + "Bash(git commit -q -m 'Plan the provider blueprint document *)", + "Bash(grep -nE '^#{1,3} ' docs/architecture.md; echo ---; sed -n '/Implemented providers/,/^## /p' README.md | head -30; echo ---; sed -n 140,180p config/manager/manager.yaml; echo ---; cat Dockerfile; echo ---; ls -a | grep -i golangci; echo ---; grep -nE 'Placement|placement|Region|Zone|MachineType|Image ' api/v1alpha1/proxy_types.go | head -40; echo ---; grep -nE 'Type|type' api/v1alpha1/providerhealth_types.go | head -20; echo ---; grep -n -i 'placement' docs/api.md | head; echo ---; grep -nE 'WithTimeout|Deadline|timeout' internal/controller/proxy_controller.go internal/gc/gc.go internal/providerhealth/monitor.go | head -20; echo ---; cat .gitea/workflows/build.yaml | sed -n 1,60p)", + "WebFetch(domain:pve.proxmox.com)", + "Bash(git -c credential.helper='!f\\(\\) { echo username=kacerr; echo \"password=$\\(tr -d \"[:space:]\" < ~/.config/gitea-token\\)\"; }; f' push -u origin feat/provider-blueprint)", + "Bash(curl -s -H \"Authorization: token $\\(tr -d '[:space:]' < ~/.config/gitea-token\\)\" \"https://gitea.home.hrajfrisbee.cz/api/v1/repos/kacerr/egress-proxies-operator/pulls?state=open\")", + "Bash(curl -s -H \"Authorization: token $\\(tr -d '[:space:]' < ~/.config/gitea-token\\)\" \"https://gitea.home.hrajfrisbee.cz/api/v1/repos/kacerr/egress-proxies-operator/pulls/7\")", + "Bash(curl -s -X PATCH -H \"Authorization: token $\\(tr -d '[:space:]' < ~/.config/gitea-token\\)\" -H 'Content-Type: application/json' -d @/tmp/claude-1000/-home-novakj-srv-egress-proxies-operator/7662ab9d-4540-4676-a943-0874ac6b081d/scratchpad/pr7.json \"https://gitea.home.hrajfrisbee.cz/api/v1/repos/kacerr/egress-proxies-operator/pulls/7\")", + "Bash(bin/golangci-lint run *)", + "Bash(awk '/^## The image cache/,/^## [^T]/' docs/kvm-provider.md)", + "Bash(awk '/^#+ .*Step 11/,0' docs/plans/2026-09-16-1333-kvm2-provider.md)", + "Bash(sort -t: -k2 -u)", + "Bash(awk '/kvm2/&&/^#/{f=1} f' docs/testing.md)", + "Bash(awk -F'\"' '{print $2}')", + "Bash(sed -n '/targets:/,/type: object/p' config/crd/bases/*providerhealths.yaml | sed -n 1,45p)", + "Bash(ssh -i ~/.ssh/proxy-operator_ed25519 -o IdentitiesOnly=yes -o BatchMode=yes -o ConnectTimeout=10 -o UserKnownHostsFile=~/.ssh/kvm_known_hosts -o StrictHostKeyChecking=yes novakj@beelink 'hostname; id -un; virsh -c qemu:///system version; virsh -c qemu:///system pool-list --all; virsh -c qemu:///system list --all')", + "Bash(awk '{print $1, $2}' ~/.ssh/kvm_known_hosts)", + "Bash(ssh-keygen -F beelink -f ~/.ssh/kvm_known_hosts)", + "Bash(ssh -i ~/.ssh/proxy-operator_ed25519 -o IdentitiesOnly=yes -o BatchMode=yes -o ConnectTimeout=10 -o UserKnownHostsFile=~/.ssh/kvm_known_hosts -o StrictHostKeyChecking=yes novakj@beelink.tail8c69c.ts.net 'hostname; id -un; id -Gn; virsh -c qemu:///system version; virsh -c qemu:///system pool-list --all --details; virsh -c qemu:///system list --all; command -v qemu-img')", + "Bash(qemu-img --version)", + "Bash(./probe)", + "Bash(./probe -proxyrcv 8192)", + "Bash(./probe -small)", + "Bash(sysctl net.ipv4.tcp_wmem net.ipv4.tcp_retries2)", + "Bash(GOFLAGS=-mod=mod GOPROXY=off go build -o probe .)", + "Bash(./probe -clientsnd 131072 -proxyrcv 8192)", + "Bash(truncate -s 64M gpt.raw)", + "Bash(sfdisk -l gptshort.raw)", + "Bash(sgdisk -v gptshort.raw)", + "Bash(mkdir -p /tmp/claude-1000/-home-novakj-srv-egress-proxies-operator/8db56daf-938c-48b8-8a17-084f3e1dea44/scratchpad)", + "Bash(curl -sS -m 30 -o storage_driver.c https://raw.githubusercontent.com/libvirt/libvirt/v11.6.0/src/storage/storage_driver.c)", + "Bash(GOFLAGS=-mod=mod GOPROXY=off go vet ./...)", + "Bash(GOFLAGS=-mod=mod GOPROXY=off go test -count=1 -run TestLeak -v . -args -n 1000 -transport unix)", + "Bash(curl -sS -m 30 -o storage_util.c https://raw.githubusercontent.com/libvirt/libvirt/v11.6.0/src/storage/storage_util.c)", + "Bash(awk '/^virStorageBackendVolUploadLocal/,/^}/' storage_util.c)", + "Bash(export GOFLAGS=-mod=mod GOPROXY=off)", + "Bash(awk '/WARNING: DATA RACE/{c++} c==1' race_ssh.log)", + "Bash(kubectl version *)", + "Bash(timeout 5 kubectl cluster-info)" ], "additionalDirectories": [ - "/Users/jan.novak/srv/go/egress-proxies-operator/.claude", - "/Users/jan.novak/srv/go/egress-proxies-operator/docs/plans", - "/Users/jan.novak/srv/go/egress-proxies-operator/docs", - "/Users/jan.novak/srv/go/egress-proxies-operator/docs/prompts", "/tmp", - "/Users/jan.novak/srv/go/egress-proxies-operator/docs/plans-executions" + "/home/novakj/srv/egress-proxies-operator", + "/home/novakj/srv/egress-proxies-operator/config/crd/bases" ] } } diff --git a/AGENTS.md b/AGENTS.md index c5bfbe1..2283046 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,320 +1,42 @@ -# egress-proxies-operator - AI Agent Guide - -## Project Structure - -**Single-group layout (default):** -``` -cmd/main.go Manager entry (registers controllers/webhooks) -api//*_types.go CRD schemas (+kubebuilder markers) -api//zz_generated.* Auto-generated (DO NOT EDIT) -internal/controller/* Reconciliation logic -internal/webhook/* Validation/defaulting (if present) -config/crd/bases/* Generated CRDs (DO NOT EDIT) -config/rbac/role.yaml Generated RBAC (DO NOT EDIT) -config/samples/* Example CRs (edit these) -Makefile Build/test/deploy commands -PROJECT Kubebuilder metadata Auto-generated (DO NOT EDIT) -``` - -**Multi-group layout** (for projects with multiple API groups): -``` -api///*_types.go CRD schemas by group -internal/controller//* Controllers by group -internal/webhook///* Webhooks by group and version (if present) -``` - -Multi-group layout organizes APIs by group name (e.g., `batch`, `apps`). Check the `PROJECT` file for `multigroup: true`. - -**To convert to multi-group layout:** -1. Run: `kubebuilder edit --multigroup=true` -2. Move APIs: `mkdir -p api/ && mv api/ api//` -3. Move controllers: `mkdir -p internal/controller/ && mv internal/controller/*.go internal/controller//` -4. Move webhooks (if present): `mkdir -p internal/webhook/ && mv internal/webhook/ internal/webhook//` -5. Update import paths in all files -6. Fix `path` in `PROJECT` file for each resource -7. Update test suite CRD paths (add one more `..` to relative paths) - -## Critical Rules - -### Never Edit These (Auto-Generated) -- `config/crd/bases/*.yaml` - from `make manifests` -- `config/rbac/role.yaml` - from `make manifests` -- `config/webhook/manifests.yaml` - from `make manifests` -- `**/zz_generated.*.go` - from `make generate` -- `PROJECT` - from `kubebuilder [OPTIONS]` - -### Never Remove Scaffold Markers -Do NOT delete `// +kubebuilder:scaffold:*` comments. CLI injects code at these markers. - -### Keep Project Structure -Do not move files around. The CLI expects files in specific locations. - -### Always Use CLI Commands -Always use `kubebuilder create api` and `kubebuilder create webhook` to scaffold. Do NOT create files manually. - -### E2E Tests Require an Isolated Kind Cluster -The e2e tests are designed to validate the solution in an isolated environment (similar to GitHub Actions CI). -Ensure you run them against a dedicated [Kind](https://kind.sigs.k8s.io/) cluster (not your “real” dev/prod cluster). - -## After Making Changes - -**After editing `*_types.go` or markers:** -``` -make manifests # Regenerate CRDs/RBAC from markers -make generate # Regenerate DeepCopy methods -``` - -**After editing `*.go` files:** -``` -make lint-fix # Auto-fix code style -make test # Run unit tests -``` - -## CLI Commands Cheat Sheet - -### Create API (your own types) -```bash -kubebuilder create api --group --version --kind -``` - -### Deploy Image Plugin (scaffold to deploy/manage ANY container image) - -Generate a controller that deploys and manages a container image (nginx, redis, memcached, your app, etc.): - -```bash -# Example: deploying memcached -kubebuilder create api --group example.com --version v1alpha1 --kind Memcached \ - --image=memcached:alpine \ - --plugins=deploy-image.go.kubebuilder.io/v1-alpha -``` - -Scaffolds good-practice code: reconciliation logic, status conditions, finalizers, RBAC. Use as a reference implementation. - - -### Create Webhooks -```bash -# Validation + defaulting -kubebuilder create webhook --group --version --kind \ - --defaulting --programmatic-validation - -# Conversion webhook (for multi-version APIs) -kubebuilder create webhook --group --version v1 --kind \ - --conversion --spoke v2 -``` - -### Controller for Core Kubernetes Types -```bash -# Watch Pods -kubebuilder create api --group core --version v1 --kind Pod \ - --controller=true --resource=false - -# Watch Deployments -kubebuilder create api --group apps --version v1 --kind Deployment \ - --controller=true --resource=false -``` - -### Controller for External Types (e.g., from other operators) - -Watch resources from external APIs (cert-manager, Argo CD, Istio, etc.): - -```bash -# Example: watching cert-manager Certificate resources -kubebuilder create api \ - --group cert-manager --version v1 --kind Certificate \ - --controller=true --resource=false \ - --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 \ - --external-api-domain=io \ - --external-api-module=github.com/cert-manager/cert-manager -``` - -**Note:** Use `--external-api-module=@` only if you need a specific version. Otherwise, omit `@` to use what's in go.mod. - -### Webhook for External Types - -```bash -# Example: validating external resources -kubebuilder create webhook \ - --group cert-manager --version v1 --kind Issuer \ - --defaulting \ - --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 \ - --external-api-domain=io \ - --external-api-module=github.com/cert-manager/cert-manager -``` - -## Testing & Development - -```bash -make test # Run unit tests (uses envtest: real K8s API + etcd) -make run # Run locally (uses current kubeconfig context) -``` - -Tests use **Ginkgo + Gomega** (BDD style). Check `suite_test.go` for setup. - -## Deployment Workflow - -```bash -# 1. Regenerate manifests -make manifests generate - -# 2. Build & deploy -export IMG=/:tag -make docker-build docker-push IMG=$IMG # Or: kind load docker-image $IMG --name -make deploy IMG=$IMG - -# 3. Test -kubectl apply -k config/samples/ - -# 4. Debug -kubectl logs -n -system deployment/-controller-manager -c manager -f -``` - -### API Design - -**Key markers for** `api//*_types.go`: - -```go -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status -// +kubebuilder:resource:scope=Namespaced -// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=".status.conditions[?(@.type=='Ready')].status" - -// On fields: -// +kubebuilder:validation:Required -// +kubebuilder:validation:Minimum=1 -// +kubebuilder:validation:MaxLength=100 -// +kubebuilder:validation:Pattern="^[a-z]+$" -// +kubebuilder:default="value" -``` - -- **Use** `metav1.Condition` for status (not custom string fields) -- **Use predefined types**: `metav1.Time` instead of `string` for dates -- **Follow K8s API conventions**: Standard field names (`spec`, `status`, `metadata`) - -### Controller Design - -**RBAC markers in** `internal/controller/*_controller.go`: - -```go -// +kubebuilder:rbac:groups=mygroup.example.com,resources=mykinds,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=mygroup.example.com,resources=mykinds/status,verbs=get;update;patch -// +kubebuilder:rbac:groups=mygroup.example.com,resources=mykinds/finalizers,verbs=update -// +kubebuilder:rbac:groups=events.k8s.io,resources=events,verbs=create;patch -// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete -``` - -**Implementation rules:** -- **Idempotent reconciliation**: Safe to run multiple times -- **Re-fetch before updates**: `r.Get(ctx, req.NamespacedName, obj)` before `r.Update` to avoid conflicts -- **Structured logging**: `log := log.FromContext(ctx); log.Info("msg", "key", val)` -- **Owner references**: Enable automatic garbage collection (`SetControllerReference`) -- **Watch secondary resources**: Use `.Owns()` or `.Watches()`, not just `RequeueAfter` -- **Finalizers**: Clean up external resources (buckets, VMs, DNS entries) - -### Logging - -**Follow Kubernetes logging message style guidelines:** - -- Start from a capital letter -- Do not end the message with a period -- Active voice: subject present (`"Deployment could not create Pod"`) or omitted (`"Could not create Pod"`) -- Past tense: `"Could not delete Pod"` not `"Cannot delete Pod"` -- Specify object type: `"Deleted Pod"` not `"Deleted"` -- Balanced key-value pairs - -```go -log.Info("Starting reconciliation") -log.Info("Created Deployment", "name", deploy.Name) -log.Error(err, "Failed to create Pod", "name", name) -``` - -**Reference:** https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md#message-style-guidelines - -### Webhooks -- **Create all types together**: `--defaulting --programmatic-validation --conversion` -- **When`--force`is used**: Backup custom logic first, then restore after scaffolding -- **For multi-version APIs**: Use hub-and-spoke pattern (`--conversion --spoke v2`) - - Hub version: Usually oldest stable version (v1) - - Spoke versions: Newer versions that convert to/from hub (v2, v3) - - Example: `--group crew --version v1 --kind Captain --conversion --spoke v2` (v1 is hub, v2 is spoke) - -### Learning from Examples - -The **deploy-image plugin** scaffolds a complete controller following good practices. Use it as a reference implementation: - -```bash -kubebuilder create api --group example --version v1alpha1 --kind MyApp \ - --image= --plugins=deploy-image.go.kubebuilder.io/v1-alpha -``` - -Generated code includes: status conditions (`metav1.Condition`), finalizers, owner references, events, idempotent reconciliation. - -## Distribution Options - -### Option 1: YAML Bundle (Kustomize) - -```bash -# Generate dist/install.yaml from Kustomize manifests -make build-installer IMG=/:tag -``` - -**Key points:** -- The `dist/install.yaml` is generated from Kustomize manifests (CRDs, RBAC, Deployment) -- Commit this file to your repository for easy distribution -- Users only need `kubectl` to install (no additional tools required) - -**Example:** Users install with a single command: -```bash -kubectl apply -f https://raw.githubusercontent.com////dist/install.yaml -``` - -### Option 2: Helm Chart - -```bash -kubebuilder edit --plugins=helm/v2-alpha # Generates dist/chart/ (default) -kubebuilder edit --plugins=helm/v2-alpha --output-dir=charts # Generates charts/chart/ -``` - -**For development:** -```bash -make helm-deploy IMG=/: # Deploy manager via Helm -make helm-deploy IMG=$IMG HELM_EXTRA_ARGS="--set ..." # Deploy with custom values -make helm-status # Show release status -make helm-uninstall # Remove release -make helm-history # View release history -make helm-rollback # Rollback to previous version -``` - -**For end users/production:** -```bash -helm install my-release .//chart/ --namespace --create-namespace -``` - -**Important:** If you add webhooks or modify manifests after initial chart generation: -1. Backup any customizations in `/chart/values.yaml` and `/chart/manager/manager.yaml` -2. Re-run: `kubebuilder edit --plugins=helm/v2-alpha --force` (use same `--output-dir` if customized) -3. Manually restore your custom values from the backup - -### Publish Container Image - -```bash -export IMG=/: -make docker-build docker-push IMG=$IMG -``` - -## References - -### Essential Reading -- **Kubebuilder Book**: https://book.kubebuilder.io (comprehensive guide) -- **controller-runtime FAQ**: https://github.com/kubernetes-sigs/controller-runtime/blob/main/FAQ.md (common patterns and questions) -- **Good Practices**: https://book.kubebuilder.io/reference/good-practices.html (why reconciliation is idempotent, status conditions, etc.) -- **Logging Conventions**: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md#message-style-guidelines (message style, verbosity levels) - -### API Design & Implementation -- **API Conventions**: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md -- **Operator Pattern**: https://kubernetes.io/docs/concepts/extend-kubernetes/operator/ -- **Markers Reference**: https://book.kubebuilder.io/reference/markers.html - -### Tools & Libraries -- **controller-runtime**: https://github.com/kubernetes-sigs/controller-runtime -- **controller-tools**: https://github.com/kubernetes-sigs/controller-tools -- **Kubebuilder Repo**: https://github.com/kubernetes-sigs/kubebuilder +# egress-proxies-operator + +## Sources and architecture + +- Trust `go.mod`, `Makefile`, and current code over version tables or bootstrap prose. `CLAUDE.md`'s opening architecture/commands sections are stale; this is a working operator, not an empty scaffold. +- `cmd/main.go` assembles the provider registry, reconciler, proxy health engine, in-memory lease store, discovery HTTP API, orphan GC, and provider-health monitor on one controller-runtime manager. +- `api/v1alpha1` contains namespaced `Proxy` and cluster-scoped `ProviderHealth` resources (`crawl.example.com`). Validation/defaulting uses CRD markers/CEL; there are no admission webhooks. +- `internal/provider` defines the backend contract independently of CRD types. Constructors are registered in `cmd/main.go`; `spec.provider` selects a configured **instance name**, not a provider type. `kvm` and `kvm2` are separate implementations and do not adopt each other's VMs. +- Before changing a provider, read `docs/provider-contract.md`; use `docs/provider-spec-template.md` for a new one. Callers rely on self-contained provider IDs, truthful `ErrNotFound`, idempotent create/delete, and persisted UID/creation time for orphan GC. See `docs/providers-existing.md` for implementation-specific exceptions. +- `docs/architecture.md` explains component ownership and state transitions; `docs/api.md` documents discovery/leases; `docs/testing.md` covers test boundaries and real-infrastructure setup. + +## Commands and verification + +- Go version comes from `go.mod` (currently 1.26.0). Make installs development tools into `bin/`. +- After Go changes: `make lint-fix`, then `make test`. The latter regenerates manifests/DeepCopy, formats, vets, sets up envtest, then runs non-e2e tests with `-race` and writes `cover.out`. +- Fast checks: `go test -short -race ./...` skips envtest. Focused package/test: `go test -race ./internal/lease/` or `go test -race -run TestAcquire ./internal/lease/`. +- Envtest needs local API-server/etcd binaries; use `make setup-envtest` before targeted runs. For the controller suite with the current Kubernetes minor: + ```sh + KUBEBUILDER_ASSETS="$(./bin/setup-envtest use 1.36 --bin-dir "$PWD/bin" -p path)" go test -race ./internal/controller/ -run TestControllers -ginkgo.focus='' + ``` + The Makefile derives the envtest **minor** from `go.mod`; do not replace it with a dependency patch version that may have no published binary bundle. +- Tests are mostly stdlib `testing`; controller envtest and e2e use Ginkgo/Gomega. Fake clients do not execute CEL or schema defaults: test those with envtest, and use the API's `*OrDefault` helpers in unit tests. Envtest has no kubelet, so it cannot prove Squid starts or serves traffic. +- `make lint` / `make lint-fix` builds a custom golangci-lint with the `logcheck` plugin from `.custom-gcl.yml`; a stock global binary is insufficient. `.golangci.yml` forbids `sort` (use `slices`). Logging uses controller-runtime/logr, with Kubernetes-style messages. +- Real-host tests are opt-in: `make test-integration-kvm` / `make test-integration-kvm2` require the corresponding `KVM_TEST_*` / `KVM2_TEST_*` variables from `docs/testing.md`. After provider refactors, compile-check these with `go vet -tags=integration ./...`. +- E2E requires Docker, kind, kubectl, and an isolated cluster. `make test-e2e` uses `KIND_CLUSTER=egress-proxies-operator-test-e2e`; ensure kubectl targets that cluster, especially if it already exists. On failure, run `make cleanup-test-e2e` explicitly. Compile-check with `go vet -tags=e2e ./...`. Tracing specs additionally require both `TEMPO_URL` and `OTLP_ENDPOINT`. +- `.gitea/workflows/build.yaml` runs only on tags/manual dispatch; its gate is `go vet ./...`, `go build ./...`, `go test -short ./...`. It does not replace local lint, race, or envtest verification. + +## Code generation + +- After API types or Kubebuilder markers change, run `make manifests generate`. Edit source markers, never generated `config/crd/bases/*.yaml`, `config/rbac/role.yaml`, or `**/zz_generated.*.go`. +- Keep `+kubebuilder:rbac` markers in standalone package-level comment blocks, outside function/type documentation; otherwise controller-gen can silently ignore them. Inspect `git diff config/rbac/role.yaml` after generation. +- Preserve `+kubebuilder:scaffold:*` markers and the single-group layout. Use `kubebuilder create api` / `kubebuilder create webhook` for new scaffolding; `PROJECT` is CLI-managed. + +## Behaviour and operational gotchas + +- Managed proxies are replace-on-change: `internal/controller/spechash.go` hashes placement, resolved cloud-init content, and effective port. Do not hash all of `ProxySpec`: attributes, lease limits, and health settings must not churn VMs. +- Cloud-init Secrets must carry `crawl.example.com/cloud-init: "true"`; the manager's Secret cache excludes unlabelled Secrets. Rotating labelled Secret content triggers VM replacement. +- Proxy health checks fetch **through** the proxy. The health engine supplies snapshots/events and the reconciler writes status; status-affecting transitions drive writes, so `lastHealthCheckTime` is not a last-probe heartbeat. +- Leases/cooldowns are in-memory and disappear on restart. Discovery is not leader-elected; the deployment uses one replica. Adding replicas alone does not provide shared lease capacity. +- Use `make run-dev` locally: plain `make run` omits the required `--providers-config`. Host-side probes generally cannot route to kind pod IPs; deploy in-cluster using the README quickstart to verify kubernetes-provider health. +- `make deploy IMG=...` and `make build-installer IMG=...` modify `config/manager/kustomization.yaml` in place. Check that diff before including it in a change. +- Namespace-restricted mode disables orphan GC by default. `--gc-allow-namespaced` bypasses that guard and can classify instances owned outside the visible namespace as orphans. diff --git a/config/samples/proxy_kvm2_crawl_only_one.yaml b/config/samples/proxy_kvm2_crawl_only_one.yaml new file mode 100644 index 0000000..325f68d --- /dev/null +++ b/config/samples/proxy_kvm2_crawl_only_one.yaml @@ -0,0 +1,103 @@ +# A pool of Managed crawl proxies on the kvm2 provider — the multi-proxy +# counterpart to the single proxy_kvm2.yaml sample. Everything here boots on +# the one host the kvm2 entry configures (beelink); the proxies differ in the +# attributes crawlers select on, in size, and in image. +# +# Requires the same prerequisites as proxy_kvm2.yaml: a providers-config +# entry named "kvm2-home" and the kvm-ssh Secret that the kvm and kvm2 +# providers share (docs/kvm2-provider.md). +# +# Deliberately NOT in kustomization.yaml: `kubectl apply -k config/samples` +# would boot every VM in here at once. Apply it on its own when you want the +# pool: +# +# kubectl apply -f config/samples/proxy_kvm2_crawl_pool.yaml +# +# Crawlers reach these through the discovery API by attribute, e.g. +# `GET /v1/proxies?attr.purpose=crawl&healthy=true`, or by leasing one with +# the same selector. Attribute matching is exact equality, so keep the keys +# consistent across the pool and vary only the values you want to select on. +# +# The first Proxy for an image on the host waits for that image to download; +# the rest reuse the cached copy. debian-13 and ubuntu-24.04 are separate +# downloads, so the ubuntu member below is the slow one on a cold host. +--- +# One cloud-init Secret shared by the whole pool: the proxies below point at +# it with secretRef instead of each carrying its own copy of the squid +# config. The crawl.example.com/cloud-init label is mandatory — the manager's +# cache only holds Secrets that carry it, so an unlabelled Secret is +# invisible to the operator and the Proxy never resolves. +# +# Editing this Secret changes the resolved cloud-init of every Proxy that +# references it, and that replaces each of their VMs. That is the trade for +# sharing one definition: convenient for a config rollout, disruptive if you +# only meant to touch one member. Give a member its own Secret (or an inline +# block) when it needs to diverge. +apiVersion: v1 +kind: Secret +metadata: + name: crawl-squid-cloud-init + labels: + crawl.example.com/cloud-init: "true" +type: Opaque +stringData: + # The default key name; SecretKeySelector.key defaults to user-data, so + # the proxies below omit it. + user-data: | + #cloud-config + packages: + - squid + write_files: + - path: /etc/squid/conf.d/proxy-operator.conf + content: | + http_port 3128 + http_access allow all + via off + forwarded_for off + runcmd: + - systemctl restart squid + # Optional: log in with `ssh debian@` (or `ubuntu@` on the + # ubuntu-24.04 member) to look around; `virsh console ` on the + # host works too. + # ssh_authorized_keys: + # - ssh-ed25519 AAAA... you@laptop +--- +apiVersion: crawl.example.com/v1alpha1 +kind: Proxy +metadata: + name: crawl-home-1 +spec: + mode: Managed + provider: kvm2-home + placement: + zone: beelink + machineType: small + image: debian-13 + port: 3128 + cloudInit: + secretRef: + name: crawl-squid-cloud-init + attributes: + geo: home + purpose: crawl + tier: general +# --- +# apiVersion: crawl.example.com/v1alpha1 +# kind: Proxy +# metadata: +# name: crawl-home-2 +# spec: +# mode: Managed +# provider: kvm2-home +# placement: +# zone: beelink +# machineType: small +# image: debian-13 +# port: 3128 +# cloudInit: +# secretRef: +# name: crawl-squid-cloud-init +# attributes: +# geo: home +# purpose: crawl +# tier: general diff --git a/review/REPORT.md b/docs/reviews/2026-09-17-kvm2-provider-review/REPORT.md similarity index 100% rename from review/REPORT.md rename to docs/reviews/2026-09-17-kvm2-provider-review/REPORT.md diff --git a/review/deep.md b/docs/reviews/2026-09-17-kvm2-provider-review/deep.md similarity index 100% rename from review/deep.md rename to docs/reviews/2026-09-17-kvm2-provider-review/deep.md diff --git a/review/escalations.md b/docs/reviews/2026-09-17-kvm2-provider-review/escalations.md similarity index 100% rename from review/escalations.md rename to docs/reviews/2026-09-17-kvm2-provider-review/escalations.md diff --git a/review/scope.md b/docs/reviews/2026-09-17-kvm2-provider-review/scope.md similarity index 100% rename from review/scope.md rename to docs/reviews/2026-09-17-kvm2-provider-review/scope.md diff --git a/review/sweep.md b/docs/reviews/2026-09-17-kvm2-provider-review/sweep.md similarity index 100% rename from review/sweep.md rename to docs/reviews/2026-09-17-kvm2-provider-review/sweep.md