Files
harmony/iot/iot-operator-v0/chart/crd-source/deployments.iot.nationtech.io.yaml
Jean-Gabriel Gill-Couture 92150da12a
All checks were successful
Run Check Script / check (pull_request) Successful in 2m23s
feat(iot): label-selector targeting (replace target_devices with targetSelector)
DeploymentSpec.target_devices (flat string list) is gone. In its
place, DeploymentSpec.target_selector is a minimal
LabelSelector-shaped struct (matchLabels only for now, matchExpressions
deferred until there's a real need). Devices publish a labels map
in every AgentStatus heartbeat; operator resolves the selector
against the current fleet snapshot on each reconcile + aggregator
tick.

No legacy shim — the CRD is v1alpha1 and not yet deployed in the wild.

Aggregator consequences:
  - controller and aggregator now share a StatusSnapshots map so
    selector resolution sees the same data on both sides.
  - unreported is dropped: a device that has never heartbeated is
    invisible to the selector machinery, so the field no longer
    has clean semantics. "device went dark" can come back as a
    staleness metric later if needed.
  - controller's MissingTargets error is gone: zero matches is a
    legitimate state (devices may not have joined yet). The
    controller logs and fast-requeues (15s/30s) so a just-joining
    device picks the deployment up without needing a
    cross-task subscription.

Agent + setup Score:
  - Agent config grows a [labels] section (BTreeMap); the flat
    [agent].group field is gone. group becomes just one label.
  - IotDeviceSetupConfig takes a BTreeMap<String, String> instead
    of a String group. TOML render iterates the BTreeMap (ordered)
    so idempotent change detection still works cleanly.

CLI-facing:
  - example_iot_apply_deployment: --target-device -> --to, accepts
    comma-separated key=value pairs.
  - example_iot_vm_setup: --group -> --labels, same grammar.
  - smoke-a4.sh: VM publishes group=$GROUP,device=$DEVICE_ID;
    deploys target --to device=$DEVICE_ID so single-device smoke
    behavior is preserved while exercising the selector path.

CRD regenerated via chart/regen-crd.sh. 7 contract tests + 6
operator tests pass.
2026-04-22 11:13:42 -04:00

144 lines
5.7 KiB
YAML

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: deployments.iot.nationtech.io
spec:
group: iot.nationtech.io
names:
categories: []
kind: Deployment
plural: deployments
shortNames:
- iotdep
singular: deployment
scope: Namespaced
versions:
- additionalPrinterColumns: []
name: v1alpha1
schema:
openAPIV3Schema:
description: Auto-generated derived type for DeploymentSpec via `CustomResource`
properties:
spec:
properties:
rollout:
properties:
strategy:
enum:
- Immediate
type: string
required:
- strategy
type: object
score:
properties:
data:
x-kubernetes-preserve-unknown-fields: true
type:
minLength: 1
type: string
required:
- data
- type
type: object
x-kubernetes-validations:
- message: score.type must be a valid Rust identifier matching the struct name of the score variant (e.g. PodmanV0)
rule: self.type.matches('^[A-Za-z_][A-Za-z0-9_]*$')
targetSelector:
description: |-
Label selector that picks which devices in the fleet run this deployment. Devices publish their labels via `AgentStatus`; the operator resolves the selector against the current fleet snapshot on every reconcile.
Matches the Kubernetes `LabelSelector.matchLabels` wire format so the CLI, dashboards, and kubectl tooling all speak the same selector grammar. Expressions (`In`, `NotIn`, etc.) are deferred until there's a concrete need.
properties:
matchLabels:
additionalProperties:
type: string
default: {}
type: object
type: object
required:
- rollout
- score
- targetSelector
type: object
status:
nullable: true
properties:
aggregate:
description: Per-deployment rollup aggregated from the `agent-status` bucket. Present once at least one targeted agent has heartbeated; absent on a freshly-created CR.
nullable: true
properties:
failed:
format: uint32
minimum: 0.0
type: integer
lastError:
description: Device id of the most recent device reporting a failure, with its short error message. Surfaces the top failure to the CR's status without needing per-device subresource lookups.
nullable: true
properties:
at:
type: string
deviceId:
type: string
message:
type: string
required:
- at
- deviceId
- message
type: object
lastHeartbeatAt:
description: Timestamp of the most recent agent heartbeat counted into this aggregate. "Freshness" signal — a CR whose aggregate hasn't advanced in minutes is evidence the whole fleet has gone dark.
nullable: true
type: string
pending:
format: uint32
minimum: 0.0
type: integer
recentEvents:
default: []
description: Last-N events aggregated across all target devices, most recent first. Operator caps at a handful (see operator controller).
items:
properties:
at:
type: string
deployment:
nullable: true
type: string
deviceId:
type: string
message:
type: string
severity:
type: string
required:
- at
- deviceId
- message
- severity
type: object
type: array
succeeded:
description: Count of devices where the deployment is in each phase. Always populated (zeros are valid) so the operator can patch the whole subtree atomically. With selector-based targeting there is no "unreported" counterpart — a device that has never heartbeated is invisible to the selector machinery.
format: uint32
minimum: 0.0
type: integer
required:
- failed
- pending
- succeeded
type: object
observedScoreString:
description: Last serialized score the operator pushed to NATS. Used by the operator itself for change-detection on the hot path (skip KV write + status patch when the CR is unchanged).
nullable: true
type: string
type: object
required:
- spec
title: Deployment
type: object
served: true
storage: true
subresources:
status: {}