Files
harmony/iot/iot-operator-v0/deploy/operator.yaml
Jean-Gabriel Gill-Couture e50ab741fc feat(iot-operator): Deployment CRD controller writing to NATS KV
Implement the A1 task from the IoT walking-skeleton roadmap:

- CRD (kube-derive): `iot.nationtech.io/v1alpha1/Deployment`, namespaced,
  with `targetDevices`, `score {type, data}`, `rollout.strategy`, and a
  status subresource carrying `observedScoreString`.
- Controller: `kube::runtime::Controller` + `finalizer` helper. On Apply,
  writes `<device_id>.<deployment_name>` into NATS KV bucket
  `desired-state` and patches `.status.observedScoreString` via
  server-side apply. Skips KV write + status patch when the score is
  unchanged to avoid reconcile-loop churn. On Cleanup, removes the
  per-device keys before releasing the finalizer.
- CLI: `gen-crd` subcommand prints the CRD YAML from the Rust types;
  `run` (default) starts the controller. `deploy/crd.yaml` is generated
  by that subcommand — single source of truth, no drift.
- Deploy manifests: `deploy/operator.yaml` (Namespace, SA, ClusterRole,
  ClusterRoleBinding, Deployment) and generated `deploy/crd.yaml`.

Agent fixes surfaced while aligning with the operator's key layout:

- Watch filter: was `starts_with("desired-state.<id>.")` on
  `watch_all()`; bucket name is not a key prefix, so it never matched.
  Now uses `bucket.watch("<id>.>")` with the NATS wildcard and handles
  `Put`/`Delete`/`Purge` distinctly.
- Multi-server connect: was joining `nats.urls` with `","` into a single
  malformed URL. Pass the `Vec<String>` to `ConnectOptions::connect`.
- `credentials.type` is now validated (rejects unknown discriminators)
  so a v0.2 `zitadel` config doesn't silently fall back to shared creds.

Verification on feat/iot-walking-skeleton:
- cargo clippy --no-deps -D warnings: clean (agent + operator).
- cargo fmt --check: clean.
- x86_64 + aarch64 cross-compile: both build.
- podman module unit tests: pass.
2026-04-18 09:05:56 -04:00

76 lines
1.7 KiB
YAML

apiVersion: v1
kind: Namespace
metadata:
name: iot-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: iot-operator
namespace: iot-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: iot-operator
rules:
- apiGroups: ["iot.nationtech.io"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "patch", "update"]
- apiGroups: ["iot.nationtech.io"]
resources: ["deployments/status"]
verbs: ["get", "patch", "update"]
- apiGroups: ["iot.nationtech.io"]
resources: ["deployments/finalizers"]
verbs: ["update"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: iot-operator
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: iot-operator
subjects:
- kind: ServiceAccount
name: iot-operator
namespace: iot-system
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: iot-operator
namespace: iot-system
labels:
app.kubernetes.io/name: iot-operator
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: iot-operator
template:
metadata:
labels:
app.kubernetes.io/name: iot-operator
spec:
serviceAccountName: iot-operator
containers:
- name: operator
image: ghcr.io/nationtech/iot-operator-v0:latest
imagePullPolicy: IfNotPresent
env:
- name: NATS_URL
value: nats://nats.iot-system.svc.cluster.local:4222
- name: KV_BUCKET
value: desired-state
- name: RUST_LOG
value: info
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 500m
memory: 256Mi