Skip to content

Use assertions

Chainsaw allows declaring complex assertions with a simple and no-code approach, allowing assertions based on comparisons beyond simple equality, working with arrays, and other scenarios that could not be achieved before.

Tip

Under the hood, Chainsaw uses kyverno-json assertion trees. Refer to the assertion trees documentation for more details on the supported syntax.

Basic assertion

apiVersion: chainsaw.kyverno.io/v1alpha1
kind: Test
metadata:
  name: example
spec:
  steps:
  - try:
    - assert:
        resource:
          apiVersion: apps/v1
          kind: Deployment
          metadata:
            name: coredns
            namespace: kube-system
          spec:
            replicas: 2

When asking Chainsaw to execute the assertion above, it will look for a deployment named coredns in the kube-system namespace and will compare the existing resource with the (partial) resource definition contained in the assertion.

In this specific case, if the field spec.replicas is set to 2 in the existing resource, the assertion will be considered valid. If it is not equal to 2 the assertion will be considered failed.

This is the most basic assertion Chainsaw can evaluate.

Slightly less basic assertion

apiVersion: chainsaw.kyverno.io/v1alpha1
kind: Test
metadata:
  name: example
spec:
  steps:
  - try:
    - assert:
        resource:
          apiVersion: apps/v1
          kind: Deployment
          metadata:
            labels:
              k8s-app: kube-dns
            namespace: kube-system
          spec:
            replicas: 2

This time we are not providing a resource name.

Chainsaw will look up all deployments with the k8s-app: kube-dns label in the kube-system namespace. The assertion will be considered valid if at least one deployment matches the (partial) resource definition contained in the assertion. If none match, the assertion will be considered failed.

Apart from the resource lookup process being a little bit more interesting, this kind of assertion is essentially the same as the previous one. Chainsaw is basically making a decision by comparing an actual and expected resource.

Beyond simple equality

The assertion below will check that the number of replicas for a deployment is greater than 1 AND less than 4.

Chainsaw doesn't need to know the exact expected number of replicas. The (replicas > 1 && replicas < 4) expression will be evaluated until the result is true or the operation timeout expires (making the assertion fail).

apiVersion: chainsaw.kyverno.io/v1alpha1
kind: Test
metadata:
  name: example
spec:
  steps:
  - try:
    - assert:
        resource:
          apiVersion: apps/v1
          kind: Deployment
          metadata:
            name: coredns
            namespace: kube-system
          spec:
            (replicas > `1` && replicas < `4`): true

Tip

To indicate that a key or value in the YAML document is an expression, simply place the element between parentheses:

  • this is an expression -> interpreted as a string
  • (this is an expression) -> interpreted as a JMESPath expression

Working with arrays

Chainsaw query language makes it easy to assert on arrays. You can filter and transform arrays to select what you want to assert.

Filtering

In the example below we are creating a resource, then we assert that a condition with type == 'Ready' exists and has a field matching status: 'True':

apiVersion: chainsaw.kyverno.io/v1alpha1
kind: Test
metadata:
  name: example
spec:
  steps:
  - try:
    - apply:
        resource:
          apiVersion: tempo.grafana.com/v1alpha1
          kind: TempoStack
          metadata:
            name: simplest
          spec:
            storage:
              secret:
                name: minio
                type: s3
            ...
    - assert:
        resource:
          apiVersion: tempo.grafana.com/v1alpha1
          kind: TempoStack
          metadata:
            name: simplest
          status:
            # filter conditions array to keep elements where `type == 'Ready'`
            # and assert there's a single element matching the filter
            # and that this element status is `True`
            (conditions[?type == 'Ready']):
            - status: 'True'

Iterating

Being able to filter arrays allows selecting the elements to be processed.

On top of that, Chainsaw allows iterating over array elements to validate each item separately.

apiVersion: chainsaw.kyverno.io/v1alpha1
kind: Test
metadata:
  name: example
spec:
  steps:
  - try:
    - assert:
        resource:
          apiVersion: apps/v1
          kind: Deployment
          metadata:
            labels:
              k8s-app: kube-dns
            namespace: kube-system
          spec:
            template:
              spec:
                # the `~` modifier tells Chainsaw to iterate over the array elements
                ~.(containers):
                  securityContext: {}

This assertion uses the ~ modifier and Chainsaw will evaluate descendants once per element in the array.

Comprehensive reporting

Chainsaw offers detailed resource diffs upon assertion failures.

In the example below, the assertion failure message (metadata.annotations.foo: Invalid value: "null": Expected value: "bar") is augmented with a resource diff.

It provides a clear view of discrepancies between expected and actual resources and gives more context around the specific failure (we can easily identify the owner of the offending pod for example).

| 09:55:50 | deployment | step-1   | ASSERT    | RUN   | v1/Pod @ chainsaw-rare-liger/*
| 09:56:20 | deployment | step-1   | ASSERT    | ERROR | v1/Pod @ chainsaw-rare-liger/*
    === ERROR
    ---------------------------------------------------
    v1/Pod/chainsaw-rare-liger/example-5477b4ff8c-tnhd9
    ---------------------------------------------------
    * metadata.annotations.foo: Invalid value: "null": Expected value: "bar"

    --- expected
    +++ actual
    @@ -1,10 +1,16 @@
      apiVersion: v1
      kind: Pod
      metadata:
    -  annotations:
    -    foo: bar
        labels:
          app: nginx
    +  name: example-5477b4ff8c-tnhd9
        namespace: chainsaw-rare-liger
    +  ownerReferences:
    +  - apiVersion: apps/v1
    +    blockOwnerDeletion: true
    +    controller: true
    +    kind: ReplicaSet
    +    name: example-5477b4ff8c
    +    uid: 118abe16-ec42-4894-83db-64479c4aac6f
      spec: {}
| 09:56:20 | deployment | step-1   | TRY       | DONE  |

Next step

To continue our exploration of the main Chainsaw features, let's look at bindings and resource templating next.