Graph
A Graph is a namespaced set of nodes that create, read, and contribute to Kubernetes resources, connected by CEL expressions. kro reconciles it directly, without generating a new API.
Alpha Feature
The Graph API is alpha and disabled by default. It requires the GraphKind
feature gate. See the Graph overview.
API Specification
API Version
kro.run/v1alpha1Kind
GraphScope
NamespacedFields Reference
Spec
| Field | Type | Description |
|---|---|---|
nodesrequired | []object | Nodes is the unordered list of nodes that make up this Graph. Evaluation order is derived from inter-node CEL references, not from list order. Node IDs must be unique within the Graph. |
def | object | Def introduces raw data into scope without reading or writing any Kubernetes resource. The value is a free-form object whose fields may contain CEL expressions. |
forEach | []object | ForEach expands this node into a collection. Each entry binds a variable name to a CEL expression that evaluates to an array; the controller produces one instance per element. Multiple dimensions form the cartesian product of their bindings. |
graph | object | Graph nests another Graph as a child scope under this node's ID. The child's nodes form a lexical frame: they may reference this Graph's nodes (capture) and shadow their names, but a single CEL expression may not mix the two scopes — it references one frame or the other. The child's node outputs are addressable under this node's ID, e.g. `${nodeID.childNode.field}`. Nesting has no depth limit. The payload is a GraphSpec (a `nodes:` list); it is parsed at compile time, so the CRD stores it as an opaque object. |
idrequired | string | ID is the handle that other nodes use to reference this node from CEL expressions. Must be alphanumeric (case-insensitive) and unique within the Graph. |
includeWhen | []string | IncludeWhen is a list of CEL expressions that must all evaluate to true for this node to be applied. Evaluated against scope before apply, so expressions may reference upstream nodes. When any is false the node is skipped entirely — no resolve, no apply, no scope publication. The skip is contagious: nodes depending on a skipped node are themselves skipped (not evaluated, no error), so a disabled branch prunes cleanly instead of breaking its dependents. |
patch | object | Patch contributes fields to a resource this node does not own, authored as a raw partial manifest exactly like Template (apiVersion, kind, metadata.name required, metadata.namespace optional, plus the contributed fields). The target identified by apiVersion, kind, and metadata.name (+ namespace) must already exist; the node applies the contributed fields under a dedicated field manager (server-side apply) without taking ownership of the whole object. On prune the contributed fields are released — the field manager relinquishes them — but the target object is never deleted. The target subresource is derived from field presence rather than declared explicitly: a top-level `status` key routes the apply through the status subresource, while any other top-level key (or any metadata field beyond name/namespace) routes to the main resource. A single patch node may not mix status fields with main-resource fields, and it must contribute at least one field beyond identity. |
readyWhen | []string | ReadyWhen is a list of CEL expressions that must all evaluate to true for this node to be considered ready. Evaluated against scope after the node has been applied and its value published, so expressions typically reference the node's own published state (e.g. `cluster.status.phase == 'Active'`). Empty means the node is ready as soon as it is applied. For collection nodes (forEach) each expression is evaluated once per item with `each` bound to that item, and the node is ready only when every item satisfies every expression (use `each`, not an aggregate over the node's own name): readyWhen: [ "${each.status.phase == 'Running'}" ] |
ref | object | Ref imports a resource that exists outside this Graph into scope. The referenced resource is read-only; its fields become available to other nodes through CEL expressions. |
template | object | Template declares that this node creates and manages a Kubernetes resource. The controller applies the resource on create and on change, and deletes it on prune. |
serviceAccountName | string | ServiceAccountName, when set, causes kro to apply this Graph's resources
while impersonating the named ServiceAccount instead of using the kro
controller's own identity. The ServiceAccount is always resolved in the
Graph's own namespace (system:serviceaccount: |
Status
| Field | Type | Description |
|---|---|---|
appliedServiceAccount | string | AppliedServiceAccount is the impersonation username
(system:serviceaccount: |
conditions | []object | Conditions represent the latest available observations of the Graph's state. |
lastTransitionTime | string | Last time the condition transitioned from one status to another. |
message | string | A human-readable message indicating details about the transition. |
observedGeneration | integer | observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. |
reason | string | The reason for the condition's last transition. |
statusrequired | string | Status of the condition, one of True, False, Unknown. |
typerequired | string | Type is the type of the Condition |
contributions | []object | Contributions is the authoritative release inventory for this Graph's patch nodes: each entry records a field-manager contribution the Graph applied to a resource it does not own. Because a patch never owns its target, teardown/prune cannot rediscover these fields from ownership — the inventory is what lets Release relinquish exactly the fields the Graph contributed. The controller write-aheads the intended set BEFORE apply and rewrites it with the observed set AFTER a clean apply, so a crash in that window still leaves teardown a superset to release from. Persisted on the status subresource (not a metadata annotation) so it is RBAC-separable: a principal with only spec/metadata edit rights cannot forge the release inventory. |
apiVersionrequired | string | APIVersion of the patched target ("apps/v1", "v1", ...). |
fieldManagerrequired | string | FieldManager is the dedicated server-side-apply field manager the contributed fields were applied under. Release relinquishes the fields owned by this manager on the target. |
kindrequired | string | Kind of the patched target ("Deployment", "ConfigMap", ...). |
namerequired | string | Name of the patched target. |
namespace | string | Namespace of the patched target. Empty for cluster-scoped targets. |
subresource | string | Subresource the contribution was applied through ("status" for a status patch, empty for the main resource). |
managedResources | []object | ManagedResources is the authoritative list of cluster resources this Graph has applied. Entries are recorded in topological apply order so reverse iteration gives reverse-apply order on delete + prune. Status is persisted after reconciliation: on a fully-successful apply and prune, it reflects the currently-applied set; on errors, it preserves the union of previously-known and newly-applied resources. MaxItems bounds the inventory so a runaway forEach expansion cannot push the Graph object past etcd's object-size limit (~1.5Mi) — which would fail the status write and, because teardown reads this list, jeopardize cleanup. The practical limiter is the per-node forEach cap (runtime.DefaultMaxCollectionSize, default 1000); this ceiling is set well above any realistic aggregate (each entry is a few short strings + a UID, so 5000 entries stays comfortably under the etcd limit even during the write-ahead phase, which transiently holds previous ∪ next). |
apiVersionrequired | string | APIVersion of the resource ("apps/v1", "v1", ...). |
kindrequired | string | Kind of the resource ("Deployment", "ConfigMap", ...). |
namerequired | string | Name of the resource. |
namespace | string | Namespace of the resource. Empty for cluster-scoped resources. |
nodeIDrequired | string | NodeID is the Graph node that produced this resource. Multiple resources may share a NodeID for forEach expansions. |
uid | string | UID returned by the API server when the resource was applied. Used as a delete precondition so we don't remove a resource that was deleted-and-recreated out of band between apply and prune. A UID-less entry is a not-yet-observed intent (e.g. a pre-apply write-ahead entry declared but not yet applied this cycle). Such an entry is intentionally SKIPPED on delete/prune: without a captured UID kro cannot prove the live object is the one it applied, and deleting by name alone could remove an object kro does not own. It is therefore effectively required for cleanup — an entry only becomes deletable once a successful apply has recorded its UID. |