Files
harmony/iot/iot-operator-v0/deploy/crd.yaml
Jean-Gabriel Gill-Couture d21bdef050
Some checks are pending
Run Check Script / check (pull_request) Waiting to run
feat(iot-operator): CEL-validate score.type as a Rust identifier
The CRD previously accepted any string for `score.type`, so typos like
`"pdoman"` or `"PodmnV0"` would be persisted by the apiserver and only
surface on-device as agent-side deserialize warnings. That class of
failure is distasteful and hard to debug.

Replace the auto-derived schema for `ScorePayload` with a hand-rolled
one that keeps the same visible shape but adds two apiserver-level
guardrails:

- `score.type` gets `minLength: 1` and an `x-kubernetes-validations`
  CEL rule requiring it to match `^[A-Za-z_][A-Za-z0-9_]*$` — a valid
  Rust identifier, since score variants *are* Rust struct names in
  `harmony::modules::podman::IotScore`. Message points operators at
  the concrete example `PodmanV0`.
- `score.data` still carries only `x-kubernetes-preserve-unknown-
  fields: true`. The rule validates the discriminator's *shape*, not
  its *value*, so v0.3+ variants (OkdApplyV0, KubectlApplyV0) don't
  require an operator release — preserves ROADMAP §6.1's
  generic-router design.

The `x-kubernetes-preserve-unknown-fields` extension stays scoped to
`score.data` alone; every other field in the CRD has a strict schema,
exactly one preserve-unknown-fields marker and exactly one
validations block in the whole document.

Smoke test extended: phase 2b applies a CR with `score.type: "has
spaces"` and asserts the apiserver rejects it with the CEL message
before the operator ever sees it. Positive phases (kubectl apply ->
NATS KV put -> status observed -> delete -> KV key removed) still
PASS end-to-end.

Matches the `preserve_arbitrary` pattern used by ArgoCD
(`Application.spec.source.helm.valuesObject`) and Flux
(`HelmRelease.spec.values`), both of which similarly use narrow
preserve-unknown-fields on a payload field without coupling the CRD
to their variant catalog.
2026-04-18 10:35:59 -04:00

72 lines
1.9 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_]*$')
targetDevices:
items:
type: string
type: array
required:
- rollout
- score
- targetDevices
type: object
status:
nullable: true
properties:
observedScoreString:
nullable: true
type: string
type: object
required:
- spec
title: Deployment
type: object
served: true
storage: true
subresources:
status: {}