The CRD ships with +kubebuilder enum / required / default markers, but
the webhook adds the cross-field business rules that OpenAPI schema
validation cannot express:
spec.agent.image must be setspec.agent.replicas >= 0spec.agent.mode ∈ {clone, run}, defaulting to clone when unsetspec.agent.os ∈ {linux, windows, android, macos}spec.agent.backend ∈ {cloud-hypervisor, firecracker}, defaulting to cloud-hypervisor when unsetspec.agent.connType ∈ {ssh, rdp, vnc, adb}os=windows is rejected (FC cannot boot Windows guests)mode=clone is rejected, including an unset mode, which defaults to clone (FC snapshot/restore freezes guest network state; use mode=run)spec.toolboxes[*].name is required, unique, matches RFC 1123, and is not purely numeric (reserved for agent slot indexing)spec.toolboxes[*].mode ∈ {run, clone, static}, defaulting to run when unset — so an unset toolbox mode does not trip the firecracker+clone rule abovespec.toolboxes[*] static mode requires both staticIP and staticVMIDspec.toolboxes[*] non-static modes require image, and get the same os/connType/backend enum checks and firecracker-Windows/cloudimg-URL rules as spec.agentspec.toolboxes[*].backend must match spec.agent.backend (static toolboxes skip this check)spec.toolboxes[*] static-mode entries: connType may be left unset (falls back to vnc when vncPort is set, otherwise by OS: Windows→rdp, Android→adb, anything else→ssh); a non-empty value must be one of ssh / rdp / vnc / adbspec.agent.image, spec.toolboxes[*].image) must be a relative repo[:tag] or repo@sha256:<64 hex> (or sha512) with a lowercase repo path — a registry port, a malformed digest, a tag and a digest together, and uppercase repo characters are rejected (a bare registry host such as ghcr.io/... is accepted; the tag keeps the OCI tag character set), because the snapshot pull path resolves images under the org registry base and has no external-ref fallbackspec.snapshotPolicy ∈ {always, main-only, never}spec.hibernatePolicy ∈ {retain, release}Managed VM names are limited to 46 characters so that appending
-hibernate-import still fits the engine’s 63-character snapshot name limit.
A macOS agent or toolbox never hibernates or forks, so its VM name gets the
whole 63 characters.
This also leaves room for the shorter fork- snapshot prefix. Validation uses
the naming functions from cocoon-common:
vk-<namespace>-<cocoonset>-<slot>-<hash>, where <hash> is six
hex digits derived from the namespace and pod name; dots in the CocoonSet name
become dashes. The main agent occupies slot 0; spec.agent.replicas counts
additional agents in slots 1 through that value. The validator checks the
largest slot, including its decimal digit count.vk-<namespace>-<cocoonset>-<toolbox-name>-<hash> and
have the same budget. Static toolboxes use external VMs and skip this local VM
name check; their existing name and connection checks still apply.For example, in namespace default, a 26-character CocoonSet name fits with
slots 0 through 9. A 27-character name is rejected even with zero sub-agents;
scaling the 26-character name to 10 sub-agents is also rejected. Shorten the
CocoonSet or toolbox name, or use a shorter namespace. Names are not truncated
or rewritten, and this check does not rename existing resources.
These rules run on CocoonSet CREATE, and on UPDATE only when the spec
changed — a spec-unchanged UPDATE (a finalizer or metadata patch) is
skipped, so an invalid CR that predates stricter validation stays
deletable. They run behind the POST /validate-cocoonset endpoint —
see Overview for where that fits among the webhook’s
other endpoints.
POST /validate-cocoonhibernation gates CREATE only (an UPDATE is skipped —
retargeting spec.podRef is already blocked by the CRD’s CEL rule):
spec.desire ∈ {Hibernate, Wake}metadata.name must equal spec.podRef.name, so two racing CREATEs for one
pod collide on apiserver name uniqueness instead of both being admittedspec.desire on the existing CR instead of creating a second