Skip to main content
Version: 0.10.0-rc.0

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/v1alpha1
Kind
Graph
Scope
Namespaced

Fields Reference

Spec

FieldTypeDescription
nodesrequired
[]objectNodes 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
objectDef 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
[]objectForEach 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
objectGraph 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
stringID 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
[]stringIncludeWhen 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
objectPatch 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
[]stringReadyWhen 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
objectRef 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
objectTemplate 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
stringServiceAccountName, 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::), so a Graph can never escalate beyond the RBAC granted to a ServiceAccount in its own namespace. When empty, kro impersonates the default ServiceAccount of the Graph's namespace, confining resource access to that namespace by default. The kro controller ServiceAccount must be granted the "impersonate" verb on serviceaccounts for this to take effect.

Status

FieldTypeDescription
appliedServiceAccount
stringAppliedServiceAccount is the impersonation username (system:serviceaccount::) the Graph last applied its resources under. Teardown resolves the executor from THIS identity rather than the current spec.serviceAccountName, so editing that field between apply and delete cannot strand resources under an identity that can no longer see them. Empty for a Graph that has never applied (or one last applied by a kro version predating this field), in which case teardown falls back to the current spec.
conditions
[]objectConditions represent the latest available observations of the Graph's state.
lastTransitionTime
stringLast time the condition transitioned from one status to another.
message
stringA human-readable message indicating details about the transition.
observedGeneration
integerobservedGeneration 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
stringThe reason for the condition's last transition.
statusrequired
stringStatus of the condition, one of True, False, Unknown.
typerequired
stringType is the type of the Condition
contributions
[]objectContributions 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
stringAPIVersion of the patched target ("apps/v1", "v1", ...).
fieldManagerrequired
stringFieldManager 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
stringKind of the patched target ("Deployment", "ConfigMap", ...).
namerequired
stringName of the patched target.
namespace
stringNamespace of the patched target. Empty for cluster-scoped targets.
subresource
stringSubresource the contribution was applied through ("status" for a status patch, empty for the main resource).
managedResources
[]objectManagedResources 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
stringAPIVersion of the resource ("apps/v1", "v1", ...).
kindrequired
stringKind of the resource ("Deployment", "ConfigMap", ...).
namerequired
stringName of the resource.
namespace
stringNamespace of the resource. Empty for cluster-scoped resources.
nodeIDrequired
stringNodeID is the Graph node that produced this resource. Multiple resources may share a NodeID for forEach expansions.
uid
stringUID 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.

Brought to you with ♥ by SIG Cloud Provider