Chainsaw lets you run your Crossplane Compositions end-to-end against a live Kubernetes cluster, ensuring they behave as expected before they ever hit production.

Let’s see it in action. Imagine you have a Composition that provisions an AWS RDS instance.

First, you’ll need a Composition and a CompositeResourceDefinition (XRD) defined.

# apx-rds-instance.yaml
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
  name: rdsinstances.aws.example.org
spec:
  group: aws.example.org
  names:
    kind: RDSInstance
    plural: rdsinstances
  versions:
    - name: v1alpha1
      served: true
      referenceable: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                parameters:
                  type: object
                  properties:
                    dbInstanceClass:
                      type: string
                      default: db.t3.micro
                    storageGB:
                      type: integer
                      default: 20
              required:
                - parameters
            status:
              type: object
              properties:
                instanceEndpoint:
                  type: string

---
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: rdsinstance-composition
  labels:
    provider: aws
    resource: rdsinstance
spec:
  compositeTypeRef:
    apiVersion: aws.example.org/v1alpha1
    kind: RDSInstance
  resources:
    - name: rds-instance
      base:
        apiVersion: database.aws.crossplane.io/v1beta1
        kind: RDSInstance
        spec:
          forProvider:
            region: us-east-1
            skipFinalizer: false # Important for Chainsaw
          writeConnectionSecretToRef:
            namespace: crossplane-system
      patches:
        - fromFieldPath: spec.parameters.dbInstanceClass
          toFieldPath: spec.parameters.dbInstanceClass
        - fromFieldPath: spec.parameters.storageGB
          toFieldPath: spec.parameters.storageGB

Now, let’s write a Chainsaw test for this. You’ll define a Test resource that specifies the CompositeResource you want to create and the Steps Chainsaw should execute.

# tests/rds-instance-test.yaml
apiVersion: chainsaw.kyverno.io/v1alpha1
kind: Test
metadata:
  name: rds-instance-composition-test
spec:
  steps:
    - name: create-rds-instance
      try:
        - apply:
            dir: ./
            file: rds-instance-composite.yaml
    - name: wait-for-rds-instance
      try:
        - assert:
            dir: ./
            file: rds-instance-composite-ready.yaml
    - name: delete-rds-instance
      try:
        - delete:
            dir: ./
            file: rds-instance-composite.yaml

And the corresponding rds-instance-composite.yaml and rds-instance-composite-ready.yaml would look like this:

# rds-instance-composite.yaml
apiVersion: aws.example.org/v1alpha1
kind: RDSInstance
metadata:
  name: my-rds-instance
spec:
  parameters:
    dbInstanceClass: db.t3.micro
    storageGB: 30
# rds-instance-composite-ready.yaml
apiVersion: aws.example.org/v1alpha1
kind: RDSInstance
metadata:
  name: my-rds-instance
spec:
  parameters:
    dbInstanceClass: db.t3.micro
    storageGB: 30
status:
  atProvider:
    dbInstanceStatus: available # This is what we assert
  instanceEndpoint: "my-rds-instance.xxxxxxxxxxxx.us-east-1.rds.amazonaws.com" # Example endpoint

When you run chainsaw test, it does a few things:

  1. Applies the CompositeResource: It takes your RDSInstance manifest and applies it to your Kubernetes cluster. Crossplane then sees this and starts provisioning the underlying AWS RDS instance.
  2. Asserts the state: Chainsaw waits for the RDSInstance to reach a ready state. The rds-instance-composite-ready.yaml defines the expected state, including status.atProvider.dbInstanceStatus: available. Chainsaw polls the cluster until the actual resource matches this desired state or times out.
  3. Deletes the CompositeResource: Finally, it removes the RDSInstance manifest. Crossplane, seeing the resource deleted, will then trigger the deletion of the actual AWS RDS instance.

The magic here is that Chainsaw is running against a real Crossplane installation managing real cloud resources. It’s not just linting your YAML; it’s verifying the entire lifecycle.

The most surprising thing about Chainsaw is that it doesn’t just test your Composition’s creation flow. By defining delete steps and asserting intermediate states, you can effectively test upgrades, rollbacks, and even specific failure scenarios by carefully crafting your try and catch blocks within steps.

This gives you confidence that your Compositions will actually work when deployed. The next thing you’ll likely want to explore is how to integrate Chainsaw into your CI/CD pipeline to automate these tests on every commit.

Want structured learning?

Take the full Crossplane course →