All checks were successful
Run Check Script / check (pull_request) Successful in 2m23s
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.
144 lines
5.7 KiB
YAML
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: {}
|