Crossplane KCL function allows developers to use KCL (a DSL) to write composite logic without the need for repeated packaging of crossplane functions, and we support package management and the KRM KCL specification, which allows for OCI/Git source and the reuse of KCL's module ecosystem.
Check out this blog to learn more. Here's a simple example:
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: example
spec:
compositeTypeRef:
apiVersion: example.crossplane.io/v1beta1
kind: XR
mode: Pipeline
pipeline:
- step: create-a-bucket
functionRef:
name: function-go-templating
input:
apiVersion: krm.kcl.dev/v1alpha1
kind: KCLRun
source: |
# Read the XR
oxr = option("params").oxr
# Patch the XR with the status field
dxr = {
**oxr
status.dummy = "cool-status"
}
# Construct a bucket
bucket = {
apiVersion = "s3.aws.upbound.io/v1beta1"
kind = "Bucket"
metadata.annotations: {
"krm.kcl.dev/composition-resource-name" = "bucket"
}
spec.forProvider.region = option("oxr").spec.region
}
# Return the bucket and patched XR
items = [bucket, dxr]
- step: automatically-detect-ready-composed-resources
functionRef:
name: function-auto-ready
cat <<EOF | kubectl apply -f -
apiVersion: pkg.crossplane.io/v1beta1
kind: Function
metadata:
name: kcl-function
spec:
package: xpkg.upbound.io/crossplane-contrib/function-kcl:latest
EOF
To use a KCLRun
as the function config, the KCL source must be specified in the source
field. Additional parameters can be specified in the params
field. The params field supports any complex data structure as long as it can be represented in YAML. Besides, the function can load KCL codes from inline source, OCI source, Git source and FileSystem source.
- Inline source example
apiVersion: krm.kcl.dev/v1alpha1
kind: KCLRun
metadata:
name: basic
spec:
source: |
{
apiVersion = "s3.aws.upbound.io/v1beta1"
kind = "Bucket"
metadata.annotations: {
"krm.kcl.dev/composition-resource-name" = "bucket"
}
spec.forProvider.region = option("oxr").spec.region
}
- OCI source example
apiVersion: krm.kcl.dev/v1alpha1
kind: KCLRun
metadata:
name: basic
spec:
source: oci://ghcr.io/kcl-lang/crossplane-xnetwork-kcl-function
- Git source example
apiVersion: krm.kcl.dev/v1alpha1
kind: KCLRun
metadata:
name: basic
spec:
source: github.com/kcl-lang/modules/crossplane-xnetwork-kcl-function
- FileSystem source example
apiVersion: krm.kcl.dev/v1alpha1
kind: KCLRun
metadata:
name: basic
spec:
target: Resources
source: ./path/to/kcl/file.k
- Read the
ObservedCompositeResource
fromoption("params").oxr
. - Read the
ObservedComposedResources
fromoption("params").ocds
. - Read the
DesiredCompositeResource
fromoption("params").dxr
. - Read the
DesiredComposedResources
fromoption("params").dcds
. - Read the function pipeline's context from
option("params").ctx
. - Return an error using
assert {condition}, {error_message}
. - Read the PATH variables. e.g.
option("PATH")
. - Read the environment variables. e.g.
option("env")
.
You can define your custom parameters in the params
field and use option("params").custom_key
to get the custom_value
.
apiVersion: krm.kcl.dev/v1alpha1
kind: KCLRun
metadata:
name: basic
spec:
params:
custom_key: custom_value
source: oci://ghcr.io/kcl-lang/crossplane-xnetwork-kcl-function
A KRM YAML list means that each document must have an apiVersion
, kind
through the items
field or a single YAML output.
- Using the
items
field
apiVersion: krm.kcl.dev/v1alpha1
kind: KCLRun
metadata:
name: basic
spec:
source: |
items = [{
apiVersion: "ec2.aws.upbound.io/v1beta1"
kind: "Instance"
metadata.name = "instance1"
spec.forProvider.region: "us-east-2"
}, {
apiVersion: "ec2.aws.upbound.io/v1beta1"
kind: "Instance"
metadata.name = "instance2"
spec.forProvider.region: "us-east-2"
}]
- Single YAML output
apiVersion: krm.kcl.dev/v1alpha1
kind: KCLRun
metadata:
name: basic
spec:
source: |
{
apiVersion: "ec2.aws.upbound.io/v1beta1"
kind: "Instance"
metadata.name = "instance"
spec.forProvider.ami: "ami-0d9858aa3c6322f73"
spec.forProvider.instanceType: "t2.micro"
spec.forProvider.region: "us-east-2"
}
The KCL function can target various types of objects:
Default
: create new resources and set fields on the XR.Resources
: create new resources.PatchDesired
: set fields on existing DesiredComposed Resources.PatchResources
: set fields on existing resources fields. These resources will then be added to the desired resources map.XR
: set fields on the XR.
This is controlled by fields on the KCLRun
apiVersion: krm.kcl.dev/v1alpha1
kind: KCLRun
metadata:
name: basic
spec:
# default: Default
target: Default | PatchDesired | PatchResources | Resources | XR
source: |
# Omit the source field
...
To return desired composite resource connection details, include a KCL dict that produces the special CompositeConnectionDetails resource like the example:
details = {
apiVersion: "meta.krm.kcl.dev/v1alpha1"
kind: "CompositeConnectionDetails"
data: {
"connection-secret-key": "connection-secret-value"
}
}
Note: The value of the connection secret value must be base64 encoded. This is already the case if you are referencing a key from a managed resource's connectionDetails field. However, if you want to include a connection secret value from somewhere else, you will need to use the
base64.encode
function:
import base64
ocds = option("params").ocds
details = {
apiVersion: "meta.krm.kcl.dev/v1alpha1"
kind: "CompositeConnectionDetails"
data: {
"server-endpoint" = base64.encode(ocds["my-server"].Resource.status.atProvider.endpoint)
}
}
To mark a desired composed resource as ready, use the krm.kcl.dev/ready
annotation:
user = {
apiVersion: "iam.aws.upbound.io/v1beta1"
kind: "User"
metadata.name = "test-user"
metadata.annotations: {
"krm.kcl.dev/ready": "True"
}
}
You can read the XR, patch it with the status field and return the new patched XR in the item
result like this
# Read the XR
oxr = option("params").oxr
# Patch the XR with the status field
dxr = {
**oxr
status.dummy = "cool-status"
}
items = [dxr] # Omit other resources
You can directly use KCL standard libraries such as regex.match
, math.log
.
See here to study more features such as conditions and loops in KCL.
More examples can be found here
Logs are emitted to the Function's pod logs. Look for the Function pod in crossplane-system
.
Info # default
Debug # run with --debug flag
# Run code generation - see input/generate.go
$ go generate ./...
# Run tests - see fn_test.go
$ go test ./...
# Build the function's runtime image - see Dockerfile
$ docker build . --tag=kcllang/crossplane-kcl
# Build a function package - see package/crossplane.yaml
$ crossplane xpkg build -f package --embed-runtime-image=kcllang/crossplane-kcl
# Push a function package to the registry
$ crossplane --verbose xpkg push -f package/*.xpkg xpkg.upbound.io/crossplane-contrib/function-kcl:latest