Wire the composition root: flags, providers, runnables, manifests, samples, docs

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-09 17:28:11 +02:00
parent add120c033
commit c489832ce7
21 changed files with 695 additions and 138 deletions

View File

@@ -14,7 +14,7 @@ Pairs with [docs/plans/2026-08-07-1747-proxy-operator.md](../plans/2026-08-07-17
- [x] Step 7 — Discovery API (`internal/discovery/`)
- [x] Step 8 — GCP provider (`internal/provider/gcp/`)
- [x] Step 9 — Orphan GC + metrics
- [ ] Step 10 — Wiring, config, docs
- [x] Step 10 — Wiring, config, docs
- [ ] Step 11 — Tests
- [ ] Verification (vet/test/kind e2e) + commit, push, open MR
@@ -939,3 +939,73 @@ Worth noting: `prometheus/client_golang` was already in the module via
controller-runtime's metrics server, so no new dependency — `go mod tidy`
just promoted it to direct. `docs/architecture.md` gained §8 (GC sweep)
and §9 (metrics shape).
## Step 10 — Wiring, config, docs
The composition root and everything around it. `cmd/main.go` now: parses
the plan's flag set (plus `--gc-allow-namespaced` from Step 9), loads the
provider config first and fails fast, builds the registry with
`{"kubernetes": kubernetes.New, "gcp": gcp.New}`, wraps every provider in
`provider.WithMetrics`, then adds the lease store, health engine,
discovery server, and GC sweeper to one manager and hands the reconciler
its providers + health snapshotter + events channel. Metrics register on
controller-runtime's global registry with scrape-time closures (phase
counts from the cache, active leases summed from `store.Counts()`).
Two cache decisions became concrete here:
- The Secret cache is restricted to Secrets labelled
`crawl.example.com/cloud-init=true` (new constant
`v1alpha1.LabelCloudInit`) — the operator holds cluster-wide Secret
read RBAC, and an unrestricted cache would hold every Secret in scope.
Consequence documented in the README: an unlabelled referenced Secret
is invisible → `CloudInitError`.
- `--proxy-namespace` restricts the whole cache via `DefaultNamespaces`
and flips the GC sweeper's `NamespaceRestricted` guard.
Manifests: `config/manager/manager.yaml` gained the
`--providers-config` arg, the optional `DISCOVERY_TOKEN` secretKeyRef
(`optional: true` — without the Secret the API runs unauthenticated with
its loud warning), the ConfigMap volume mount, and containerPort 8090;
new `config/manager/providers_config.yaml` (kubernetes-only default) and
`config/default/discovery_service.yaml`. RBAC: the pods marker landed in
the controller RBAC block (cluster-scoped role — the kubernetes
provider's ListByTag spans namespaces). The plan's events RBAC was
deliberately omitted: nothing wires an EventRecorder, and unused verbs
are lint noise — recorded in Decisions.
Samples: `proxy_kubernetes.yaml` / `proxy_gcp.yaml` (with a working
Squid-installing cloud-init) / `proxy_external.yaml` replace the scaffold
placeholder; `providers-config.yaml` documents both provider blocks;
`hack/providers-dev.yaml` + a new `run-dev` Makefile target run locally
with plain-HTTP metrics:
```bash
make run-dev
# go run ./cmd/main.go --providers-config hack/providers-dev.yaml \
# --metrics-bind-address :8080 --metrics-secure=false
```
Docs: README rewritten per the plan (60-second architecture, kind
quickstart, in-cluster deploy incl. token Secret creation, GCP setup with
the IAM roles, the two prominent caveats, the no-substitutions version
pins note). `docs/architecture.md` gained the components table and the
full Decisions section — the plan's listed decisions plus everything
accumulated in this log (RequeueNow, quota≠Failed, 409 arithmetic,
TTL-400-not-clamp, GCP required placement, unknown-status→Stopped,
banned==rate_limited window, GC logging convention, the Secret label
contract, events-RBAC omission, logr-not-slog). `CHANGELOG.md` got its
first entry with a real timestamp.
Verified: `make test` green across the repo (coverage unchanged),
`bin/kustomize build config/default` and `config/samples` render clean,
`make build` produces the binary, e2e-tagged build + vet clean. The kind
end-to-end run is deliberately still ahead — it is the Verification
step's job, after Step 11 closes the remaining test gaps.
Worth noting: `make run-dev` passes `--metrics-secure=false` because the
scaffold's secure-serving default requires authn/authz reachability that
a local process doesn't have; in-cluster deployments keep the secure
default from the kustomize patch. The `providers` map wrapping happens
*before* any consumer sees it, so the reconciler and GC only ever hold
instrumented providers.