Writing Custom Assertion for KubeAssert

KubeAssert is a kubectl plugin used to make assertions against resources on your Kubernetes cluster from command line. It is an open source project that I created on GitHub.

As the third post of KubeAssert series, in this post, I will work you through the steps to write your own assertion to extend KubeAssert when out-of-the-box assertions provided by KubeAssert do not match your specific need.

When to Write Custom Assertion

The out of box assertions provided by KubeAssert can support most scenarios in your day to day work. However, it is also possible that these assertions can not fulfill your requirement when you have some advanced cases. KubeAssert supports custom assertion, where you can write your own assertion, and have KubeAssert load and execute it.

As an example, let’s write an assertion to verify if your kubeconfig includes a cluster where the cluster name is specified as an input argument when run the assertion from the command line.

Create a Shell Script File

Firstly, Let’s create a shell script file called custom-assertions.sh and put it into $HOME/.kubeassert directory. This is where KubeAssert loads the custom assertions. Each time when KubeAssert is started, it will find all .sh files in this directory and load them as custom assertions.

The file can include multiple assertions. Each assertion is implemented as a shell function. In our case, we only have one assertion, which is the function cluster and right now the function body is empty:

#!/bin/bashfunction cluster {
:
}

To test the assertion, you can run kubectl assert and specify the function name cluster as the assertion name:

kubectl assert cluster
ASSERT PASS

You see the assertion is passed since we have nothing added yet.

Implement the Assertion

In order to implement an assertion, usually you will get a few things to do:

  • Validate input arguments. In our case, it is the cluster name that will be input by user from the command line. If the validation is failed, you can call the utility function logger::error to print error message and exit the assertion with a non-zero code.
  • Print assertion message. This will print a message, by calling the utility function logger::assert, to tell people what you are going to assert.
  • Run some kubectl commands. This will query the cluster using kubectl.
  • Validate results. By parsing the results returned from kubectl, either fail the assertion or let it pass. If the validation is failed, you can call the utility function logger::fail to print a failure message. Otherwise, you can simply do nothing or call logger::info to print some normal logs to give user a bit more background on what is going on.

Here is our logic added to the cluster function:

#!/bin/bashfunction cluster {
# Validate input arguments
[[ -z $1 ]] && logger::error "You must specify a cluster name." && exit 1
# Print assertion message
logger::assert "Cluster with name $1 should be included in kubeconfig."
# Run some kubectl commands
kubectl config get-clusters
# Validate results
if cat $HOME/.kubeassert/result.txt | grep -q ^$1$; then
# Print normal logs
logger::info "Found $1 in kubeconfig."
else
# Print failure message
logger::fail "$1 not found."
fi
}

You may notice that to validate the results, we actually look for a file called result.txt in $HOME/.kubeassert/ directory. Our outputs that are returned by kubectl are all dumped into this file.

Now let’s try the assertion without specifying a cluster name. It will show an error message to indicate the input argument is missing.

kubectl assert cluster
ERROR You must specify a cluster name.

Specify a cluster name that does not exist. It will fail the assertion with the reason printed to the console.

kubectl assert cluster kind
ASSERT Cluster with name kind should be included in kubeconfig.
ASSERT FAIL kind not found.

Specify a cluster name that does exist. This will pass the assertion.

kubectl assert cluster kind-foo
ASSERT Cluster with name kind-foo should be included in kubeconfig.
INFO Found kind-foo in kubeconfig.
ASSERT PASS

If something goes wrong when you run the assertion, you may want to see what kubectl commands the assertion run and what the actual results they return for troubleshooting purpose, just enable the verbose logs using -v when you run the assertion.

kubectl assert cluster kind-foo -v
ASSERT Cluster with name kind-foo should be included in kubeconfig.
INFO kubectl config get-clusters
NAME
kind-foo
kind-bar
INFO Found kind-foo in kubeconfig.
ASSERT PASS

Add Comment to the Assertion

So far we have implemented our custom assertion. But there is still one more thing left. To make the custom assertion visible in the supported assertion list when you run KubeAssert with -h/--help option or without any option, you need to add one special comment ahead of the assertion function.

Also, when you run kubectl assert <assertion> with -h/--help option to output the help information for your assertion, it requires you to prepare the help information beforehand. This is also defined in the comment.

The comment should start with ## and end with ##, inside which there are a few fields where the field names are all started with @. Below is a template with detailed explanation for each field:

##
# @Name: <Input your single-line assertion name here>
# @Description: <Input your single-line assertion description here>
# @Usage: <Input your single-line assertion usage information here>
# @Options:
# <Input help information for all your options started from here>
# <It supports multiple lines>
# @Examples:
# <Input detailed information for all examples started from here>
# <It supports multiple lines>
##

For Options field, there are a few pre-defined variables which can be used. If your assertion supports some common options that have already been defined by KubeAssert, you can just put the corresponding variables in Options field as placeholder. They will be expanded to the actual content when KubeAssert prints the help information.

  • ${GLOBAL_OPTIONS}: This variable represents global options that should be applied to all assertions, e.g. -h/--help to print the help information.
  • ${SELECT_OPTIONS}: This variable represents options that are used to filter on resources in cluster, e.g. -l/--selector to filter resource by labels, and -n/--namespace to limit resource to a specific namespace.
  • ${OP_VAL_OPTIONS}: This variable represents comparison operators, e.g. -eq, -lt, -gt, -ge, -le.

By using these variables, it also unifies the definitions for all the above options across the assertions, including both pre-defined ones and custom ones.

Here are the comment for our cluster assertion:

##
# @Name: cluster
# @Description: Assert specified cluster included in kubeconfig
# @Usage: kubectl assert cluster (NAME) [options]
# @Options:
# ${GLOBAL_OPTIONS}
# @Examples:
# # To assert a cluster is included in kubeconfig
# kubectl assert cluster kind-foo
##
function cluster {
...
}

To validate it, you can run below command to see if the assertion is included in the supported assertion list:

kubectl assert --help

If all goes as expected, you will see cluster appeared in the list. Run below command to print the help information for the custom assertion:

kubectl assert cluster --help

You will see below output which is exactly what we define in the comment as above:

Assert cluster with specified name included in kubeconfig fileUsage: kubectl assert cluster (NAME) [options]Options:
-h, --help: Print the help information.
-v, --verbose: Enable the verbose log.
-V, --version: Print the version information.

Summary

To put all things together, the final version of cluster assertion can be found as below:

#!/bin/bash##
# @Name: cluster
# @Description: Assert specified cluster included in kubeconfig
# @Usage: kubectl assert cluster (NAME) [options]
# @Options:
# ${GLOBAL_OPTIONS}
# @Examples:
# # To assert a cluster is included in kubeconfig
# kubectl assert cluster kind-foo
##
function cluster {
# Validate input arguments
[[ -z $1 ]] && logger::error "You must specify a cluster name." && exit 1
# Print assertion message
logger::assert "Cluster with name $1 should be included in kubeconfig."
# Run some kubectl commands
kubectl config get-clusters
# Validate results
if cat $HOME/.kubeassert/result.txt | grep -q ^$1$; then
# Print normal logs
logger::info "Found $1 in kubeconfig."
else
# Print failure message
logger::fail "$1 not found."
fi
}

As you can see, to write custom assertion for KubeAssert is quite easy. In next post, I will show you how to integrate KubeAssert with KUTTL, which is a tool that provides a declarative approach using YAML to test Kubernetes.

You can learn more on KubeAssert by reading its online documents. If you like it, you can consider to give star to this project . Also, any contributions such as bug report and code submission are very welcome.

Life is coding and writing! I am a software engineer who have been in IT field for 10+ years. I would love to write beautiful code and story for people.