These examples demonstrate the key capabilities of function-kro, from basic resource
dependencies through conditional creation, readiness checks, external references,
collections, field omission, core Kubernetes resources, composing a Kubernetes application, and collection size limits.
Create a Kubernetes cluster, e.g. with kind:
kind create clusterInstall Crossplane:
helm repo add crossplane-stable https://charts.crossplane.io/stable
helm repo update
helm install crossplane --namespace crossplane-system --create-namespace crossplane-stable/crossplane --set args='{"--debug","--circuit-breaker-burst=500.0", "--circuit-breaker-refill-rate=5.0", "--circuit-breaker-cooldown=1m"}'Install the required functions and providers:
kubectl apply -f drc.yaml
kubectl apply -f extensions.yamlThe DeploymentRuntimeConfig (drc.yaml) enables debug logging, the alpha omit() CEL
function (--feature-gates=CELOmitFunction=true), and a low collection size limit
(--rgd-max-collection-size=5) for the collection limits example.
Wait for the functions and providers to be installed and healthy:
kubectl get pkgAWS_PROFILE=default && echo -e "[default]\naws_access_key_id = $(aws configure get aws_access_key_id --profile $AWS_PROFILE)\naws_secret_access_key = $(aws configure get aws_secret_access_key --profile $AWS_PROFILE)" > aws-credentials.txt
kubectl create secret generic aws-secret -n crossplane-system --from-file=creds=./aws-credentials.txt
kubectl apply -f providerconfig.yamlThis example demonstrates the fundamental dependency pattern and DAG approach of
function-kro. It creates a VPC with three subnets across availability zones and
a security group. Each resource depends on the VPC ID, showing how
function-kro resolves static expressions (from the XR spec) and dynamic
expressions (from composed resource status).
Create the NetworkingStack XRD and composition:
kubectl apply -f basic/xrd.yaml
kubectl apply -f basic/composition.yamlCreate a NetworkingStack instance:
kubectl apply -f basic/xr.yamlWatch the composed resources being created and the status being updated:
crossplane resource trace -w networkingstack.example.crossplane.io/cool-networkWe can see the aggregated networking info in the XR status, which includes the VPC ID, subnet IDs, and security group ID:
kubectl get NetworkingStack/cool-network -o json | jq '.status.networkingInfo'Clean up all the resources when you are done:
kubectl delete -f basic/xr.yaml
kubectl get managedkubectl delete -f basic/composition.yaml
kubectl delete -f basic/xrd.yamlThis example demonstrates conditional resource creation using includeWhen. A VPC and
private subnet are always created, but a public subnet and security group are only included
when their respective boolean flags are enabled in the XR spec (enablePublicSubnet,
enableSecurityGroup).
Create the NetworkingStack XRD and composition:
kubectl apply -f conditionals/xrd.yaml
kubectl apply -f conditionals/composition.yamlCreate a NetworkingStack instance:
kubectl apply -f conditionals/xr.yamlWatch the composed resources being created and note that conditional resources are only
included when their flags are set to true:
crossplane resource trace -w networkingstack.conditionals.example.crossplane.io/cool-networkWe can see the status reflects which resources were actually created:
kubectl get networkingstack.conditionals.example.crossplane.io/cool-network -o json | jq '.status.networkingInfo'kubectl delete -f conditionals/xr.yaml
kubectl get managedkubectl delete -f conditionals/composition.yaml
kubectl delete -f conditionals/xrd.yamlThis example demonstrates custom readiness conditions using readyWhen. Each resource
defines CEL expressions that determine when it is considered ready. For example, the VPC uses
safe field access (?. operator) to check that status.atProvider.id has a value, and the
security group checks for a Ready=True condition.
This example does not use function-auto-ready in its pipeline because we are
exclusively using readyWhen statements for each resource. Crossplane will
automatically detect that the parent XR is ready when all composed resources
have their ready state set to true in the function pipeline via the readyWhen
statements.
Create the NetworkingStack XRD and composition:
kubectl apply -f readiness/xrd.yaml
kubectl apply -f readiness/composition.yamlCreate a NetworkingStack instance:
kubectl apply -f readiness/xr.yamlWatch the composed resources being created and observe how readiness propagates as each
resource satisfies its readyWhen conditions:
crossplane resource trace -w networkingstack.readiness.example.crossplane.io/cool-networkWe can see the aggregated status once all resources are ready:
kubectl get networkingstack.readiness.example.crossplane.io/cool-network -o json | jq '.status.networkingInfo'kubectl delete -f readiness/xr.yaml
kubectl get managedkubectl delete -f readiness/composition.yaml
kubectl delete -f readiness/xrd.yamlThis example demonstrates two ways to reference existing resources using externalRef:
- Single external ref — fetch one ConfigMap by name to provide platform configuration (region, CIDR block, environment)
- External collection ref — select multiple ConfigMaps by label selector to aggregate
notification subscriber data using CEL list functions (
size(),map(),exists()). The label value itself uses a CEL expression (${schema.metadata.name}) so the selector dynamically matches ConfigMaps for the specific XR instance.
The VPC, subnet, and security group source their configuration from the single external ConfigMap. The XR status additionally surfaces aggregated data from all subscriber ConfigMaps matched by label selector.
Create the NetworkingStack XRD, composition, and the external ConfigMaps:
kubectl apply -f externalref/xrd.yaml
kubectl apply -f externalref/composition.yaml
kubectl apply -f externalref/configmap.yaml
kubectl apply -f externalref/subscribers.yamlCreate a NetworkingStack instance:
kubectl apply -f externalref/xr.yamlWatch the composed resources being created with configuration sourced from the external ConfigMap:
crossplane resource trace -w networkingstack.externalref.example.crossplane.io/cool-networkWe can see the platform configuration pulled from the single external ConfigMap:
kubectl get networkingstack.externalref.example.crossplane.io/cool-network -o json | jq '.status.platformConfig'The notification data is aggregated from all external subscriber ConfigMaps matching the
notification-channel: cool-network label. The subscriberCount, teams, and
hasOncall fields are computed using CEL list functions across the entire collection:
kubectl get networkingstack.externalref.example.crossplane.io/cool-network -o json | jq '.status.notifications'This should show the aggregated subscriber data:
{
"hasOncall": true,
"subscriberCount": "3",
"teams": "dev, platform, security"
}kubectl delete -f externalref/xr.yaml
kubectl get managedkubectl delete -f externalref/subscribers.yaml
kubectl delete -f externalref/configmap.yaml
kubectl delete -f externalref/composition.yaml
kubectl delete -f externalref/xrd.yamlThis example demonstrates iterating over array inputs using forEach to dynamically create
multiple resources. A single "subnets" resource definition with forEach iterates over an
array of availability zones from the XR spec, creating one subnet per entry. The status
uses map() to collect all subnet IDs into an array.
Create the NetworkingStack XRD and composition:
kubectl apply -f collections/xrd.yaml
kubectl apply -f collections/composition.yamlCreate a NetworkingStack instance:
kubectl apply -f collections/xr.yamlWatch the composed resources being created and note that a subnet is created for each availability zone in the input array:
crossplane resource trace -w networkingstack.collections.example.crossplane.io/cool-networkWe can see the array of subnet IDs collected from all dynamically created subnets:
kubectl get networkingstack.collections.example.crossplane.io/cool-network -o json | jq '.status.networkingInfo'kubectl delete -f collections/xr.yaml
kubectl get managedkubectl delete -f collections/composition.yaml
kubectl delete -f collections/xrd.yamlThis example demonstrates conditional field removal using omit(), an alpha CEL function
that removes a field from the desired state entirely rather than setting it to a zero value.
This distinction matters for Server-Side Apply (SSA) — when a field is absent from the
desired state, Crossplane does not claim ownership of it, allowing other controllers or
provider defaults to manage it independently.
The composition creates a VPC where enableDnsHostnames is controlled by the XR spec. When
the flag is false, omit() removes the field from the desired VPC instead of setting it to
false. When the flag is true, the field is present and set to true.
Create the NetworkingStack XRD and composition:
kubectl apply -f omit/xrd.yaml
kubectl apply -f omit/composition.yamlCreate a NetworkingStack instance with DNS hostnames disabled:
kubectl apply -f omit/xr.yamlWatch the composed resources being created:
crossplane resource trace -w networkingstack.omit.example.crossplane.io/cool-networkWe can inspect the composed VPC's forProvider spec and confirm that enableDnsHostnames
is absent — omit() removed it entirely rather than setting it to false:
kubectl get vpc.ec2.aws.m.upbound.io -l crossplane.io/composite=cool-network -o json | jq '.items[0].spec.forProvider'Now patch the XR to enable DNS hostnames and stop the omit() function from
omitting this value on the composed VPC:
kubectl patch networkingstack.omit.example.crossplane.io/cool-network --type merge -p '{"spec":{"enableDnsHostnames":true}}'Inspect the VPC again to see enableDnsHostnames appear in forProvider:
kubectl get vpc.ec2.aws.m.upbound.io -l crossplane.io/composite=cool-network -o json | jq '.items[0].spec.forProvider'kubectl delete -f omit/xr.yaml
kubectl get managedkubectl delete -f omit/composition.yaml
kubectl delete -f omit/xrd.yamlThis example demonstrates support for core Kubernetes resources. A Secret is always created both in Crossplane <2.2.0 without required_schemas capability and in Crossplane >2.2.0 where that capability is supported.
Create the CoolSecret XRD and composition:
kubectl apply -f core/xrd.yaml
kubectl apply -f core/composition.yamlCreate a CoolSecret instance:
kubectl apply -f core/xr.yamlWatch the XR become ready when its composed resources satisfy their readyWhen conditions:
crossplane resource trace -w coolsecret.core.example.crossplane.io/cool-secretkubectl delete -f core/xr.yaml
kubectl delete -f core/composition.yaml
kubectl delete -f core/xrd.yamlThis example mirrors the upstream Crossplane composition getting-started
guide:
an App XR composes a native Kubernetes Deployment and Service, then
aggregates their observed state back into the XR status. It needs no cloud
provider or credentials.
It also exercises function-kro's handling of integer fields. status.replicas is
an integer read from the Deployment's observed availableReplicas. Crossplane
delivers every observed number to functions as a float64, so this field only
populates correctly because function-kro normalizes whole numbers back to
integers before evaluation.
Create the App XRD and composition:
kubectl apply -f app/xrd.yaml
kubectl apply -f app/composition.yamlCreate an App instance:
kubectl apply -f app/xr.yamlWatch the App become ready once the Deployment and Service satisfy their
readyWhen conditions:
crossplane resource trace app.example.crossplane.io/my-app --watchOnce the Deployment has rolled out, confirm both aggregated status fields are
populated: replicas equals the Deployment's available replica count (2) and
address holds the Service's cluster IP:
kubectl get app.example.crossplane.io/my-app -o json | jq '.status | {replicas, address}'kubectl delete -f app/xr.yaml
kubectl delete -f app/composition.yaml
kubectl delete -f app/xrd.yamlThis example demonstrates the collection size limit guardrail that prevents forEach
expansions from creating too many resources. The function is configured with
--rgd-max-collection-size=5 via the shared DeploymentRuntimeConfig, so any collection
that expands beyond 5 items produces a fatal error.
The composition creates subnets dynamically via forEach over an array of availability
zones from the XR spec. We start with 3 availability zones (under the limit), then expand
to 6 to trigger the guardrail.
Create the NetworkingStack XRD and composition:
kubectl apply -f collection-limits/xrd.yaml
kubectl apply -f collection-limits/composition.yamlCreate a NetworkingStack instance with 3 availability zones:
kubectl apply -f collection-limits/xr.yamlWatch the composed resources being created — 3 subnets are within the limit of 5:
crossplane resource trace -w networkingstack.collectionlimits.example.crossplane.io/cool-networkWe can see the array of subnet IDs collected from all dynamically created subnets:
kubectl get networkingstack.collectionlimits.example.crossplane.io/cool-network -o json | jq '.status.networkingInfo'Now apply the XR with 6 availability zones, exceeding the collection size limit:
kubectl apply -f collection-limits/xr-exceed.yamlWatch the trace to see the fatal error indicating the collection size was exceeded:
crossplane resource trace networkingstack.collectionlimits.example.crossplane.io/cool-network -o widekubectl delete -f collection-limits/xr.yaml
kubectl get managedkubectl delete -f collection-limits/composition.yaml
kubectl delete -f collection-limits/xrd.yaml