JMESPath

The JSON query language behind Kyverno.

This page is currently under construction.

JMESPath (pronounced “James path”) is a JSON query language created by James Saryerwinnie and is the language that Kyverno supports to perform more complex selections of fields and values and also manipulation thereof by using one or more filters. If you’re familiar with kubectl and Kubernetes already, this might ring a bell in that it’s similar to JSONPath. JMESPath can be used almost anywhere in Kyverno although is an optional component depending on the type and complexity of a Kyverno policy or rule that is being written. While many policies can be written with simple overlay patterns, others require more detailed selection and transformation. The latter is where JMESPath is useful.

While the complete specifications of JMESPath can be read on the official site’s specifications page, much of the specifics may not apply to Kubernetes use cases and further can be rather thick reading. This page serves as an easier guide and tutorial on how to learn and harness JMESPath for Kubernetes resources for use in crafting Kyverno policies. It should not be a replacement for the official JMESPath documentation but simply a use case specific guide to augment the already comprehensive literature.

Getting Set Up

In order to position yourself for success with JMESPath expressions inside Kyverno policies, a few tools are recommended.

  1. kubectl, the Kubernetes CLI here. While having kubectl is a given, it comes in handy especially when building a JMESPath expression around performing API lookups.

  2. kyverno, the Kyverno CLI here or via krew. Kyverno acts as a webhook (when run in-cluster) but also as a standalone CLI when run outside giving you the ability to test policies and, more recently, to test custom JMESPath filters which are endemic to only Kyverno. With the jp subcommand, it contains the functionality present in the upstream jp CLI tool and also newer capabilities. It effectively allows you to test out JMESPath expressions live in a command line interface by passing in a JSON document and seeing the results without having to repeatedly test Kyverno policies.

  3. yq, the YAML processor here. yq allows reading from a Kubernetes manifest and converting to JSON, which is helpful in order to be piped to jp in order to test expressions.

  4. jq, the JSON processor here. jq is an extremely popular tool for working with JSON documents and has its own filter ability, but it’s also useful in order to format JSON on the terminal for better visuals.

Basics

JMESPath is used when you need fine-grained selection of a document and need to perform some type of query logic against the result. For example, if in a given field you need to refer to the value of another field either in the same resource or in a different one, you’ll need to use JMESPath. This sample policy performs a simple mutation on a Pod to add a new label named appns and set the value of it based on the value of the Namespace in which that Pod is created.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: add-labels
 5spec:
 6  rules:
 7  - name: add-labels
 8    match:
 9      any:
10      - resources:
11          kinds:
12          - Pod
13    mutate:
14      patchStrategicMerge:
15        metadata:
16          labels:
17            appns: "{{request.namespace}}"

JMESPath expressions in most places in Kyverno must be enclosed in double curly braces like {{request.namespace}}. If an expression is used as the value of a field and contains nothing else, the expression needs to be wrapped in quotes: appns: "{{request.namespace}}". If the value field contains other text outside of the expression, then it can be unquoted and treated as a string but this isn’t strictly required: message: The namespace name is {{request.namespace}}.

When building a JMESPath expression, a dot (.) character is called a “sub-expression” and used to descend into nested structures. In the {{request.namespace}} example, this expression is looking for the top-most object the key of which is called request and then looking for a child object the key of which is called namespace. Whatever the value of the namespace key is will be inserted where the expression is written. Given the below AdmissionReview snippet, which will be explained in a moment, the value that would result from the {{request.namespace}} expression is foo.

1{
2    "apiVersion": "admission.k8s.io/v1",
3    "kind": "AdmissionReview",
4    "request": {
5        "namespace": "foo"
6    }
7}

When submitting a Pod, which matches the policy above, the result which gets created after Kyverno has mutated it would then look something like this.

Incoming Pod

1apiVersion: v1
2kind: Pod
3metadata:
4  name: mypod
5spec:
6  containers:
7  - name: busybox
8    image: busybox

Outgoing Pod

 1apiVersion: v1
 2kind: Pod
 3metadata:
 4  name: mypod
 5  labels:
 6    appns: foo
 7spec:
 8  containers:
 9  - name: busybox
10    image: busybox

Notice in the highlighted lines that the new label appns has been added to the Pod and the value set equal to the expression {{request.namespace}} which, in this instance, happened to be foo because it was created in the foo Namespace. Should this Pod be created in another Namespace called bar, as you might guess, the label would be appns: bar. And this is a nice segue into AdmissionReview resources.

AdmissionReview

Kyverno is an example, although there are many others, of an admission controller. As the name implies, these are pieces of software which have some stake in whether a given resource is admitted into the cluster or not. They may be either validating, mutating, or both. The latter applies to Kyverno as it has both capabilities. For a graphical representation of the order in which these requests make their way into Kyverno, see the introduction page.

When a resource that matches the criteria of a selection statement gets sent to the Kubernetes API server, after the API server performs some basic modifications to it, it then gets sent to webhooks which have told the API server via a MutatingWebhookConfiguration or ValidatingWebhookConfiguration resource–which Kyverno creates for you based upon the policies you write–that it wishes to be informed. The API server will “wrap” the matching resource in another resource called an AdmissionReview which contains a bunch of other descriptive data about that request, for example what type or request this is (like a creation or deletion), the user who submitted the request, and, most importantly, the contents of the resource itself. Given the simple Pod example above that a user wishes to be created in the foo Namespace, the AdmissionReview request that hits Kyverno might look like following.

 1{
 2    "kind": "AdmissionReview",
 3    "apiVersion": "admission.k8s.io/v1",
 4    "request": {
 5        "uid": "3d4fc6c1-7906-47d9-b7da-fc2b22353643",
 6        "kind": {
 7            "group": "",
 8            "version": "v1",
 9            "kind": "Pod"
10        },
11        "resource": {
12            "group": "",
13            "version": "v1",
14            "resource": "pods"
15        },
16        "requestKind": {
17            "group": "",
18            "version": "v1",
19            "kind": "Pod"
20        },
21        "requestResource": {
22            "group": "",
23            "version": "v1",
24            "resource": "pods"
25        },
26        "name": "mypod",
27        "namespace": "foo",
28        "operation": "CREATE",
29        "userInfo": {
30            "username": "thomas",
31            "uid": "404d34c4-47ff-4d40-b25b-4ec4197cdf63"
32        },
33        "object": {
34            "kind": "Pod",
35            "apiVersion": "v1",
36            "metadata": {
37                "name": "mypod",
38                "creationTimestamp": null
39            },
40            "spec": {
41                "containers": [
42                    {
43                        "name": "busybox",
44                        "image": "busybox",
45                        "resources": {}
46                    }
47                ]
48            },
49            "status": {}
50        },
51        "oldObject": null,
52        "dryRun": false,
53        "options": {
54            "kind": "CreateOptions",
55            "apiVersion": "meta.k8s.io/v1"
56        }
57    }
58}

As can be seen, the full Pod is represented along with other metadata surrounding its creation.

These AdmissionReview resources serve as the most common source of data when building JMESPath expressions, specifically request.object. For the other data properties which can be consumed via an AdmissionReview resource, refer back to the variables page.

Formatting

Because there are various types of values in differing fields, there are differing ways values must be supplied to JMESPath expressions as inputs in order to generate not only a valid expression but produce the output desired. Specifying values in the correct format is key to this success. Values which are supported but need to be differentiated in formatting are numbers (i.e., an integer like 6 or a floating point like 6.7), a quantity (i.e., a number with a unit of measure like 6Mi), a duration (i.e., a number with a unit of time like 6h), a semver (i.e., a version number like 1.2.3), and others. Because Kyverno (and therefore most custom JMESPath filters built for Kyverno) is designed for Kubernetes, it is Kubernetes aware. Therefore, specifying `6` as an input to a filter is not the same as specifying '6' where the former is interpreted as “the number six” and latter as “six bytes”. The types which map to the possible values are either JSON or string. In JMESPath, these are literal expression and raw string literals. Use the table below to find how to format the type of value which should be supplied.

Value Type Input Type JMESPath Type Formatting
Number Integer Literal backticks
Quantity String Raw quotes
Duration String Raw quotes
Labels (map) Object Literal backticks

Paths in a JMESPath expression may also need escaping or literal quoting depending on the contents. For example, in a ResourceQuota the following schema elements may be present:

1spec:
2  hard:
3    limits.memory: 3750Mi
4    requests.cpu: "5"

To represent the limits.memory field in a JMESPath expression requires literal quoting of the key in order to avoid being interpreted as child nodes limits and memory. The expression would then be {{ spec.hard.\"limits.memory\" }}. A similar approach is needed when individual keys contain special characters, for example a dash (-). Quoting and then escaping is similarly needed there, ex., {{ images.containers.\"my-container\".tag }}.

Quoting of an overall JMESPath expression can also impact how it is evaluated. For fields which only contain a JMESPath expression (ex., key: "{{ request.object.spec.template.spec.containers[].image | contains(@, 'nginx') }}") it is important to use double quotes on the outer expression (as shown) and single quotes for input fields of type string. Even if no JMESPath filters are used, any expression should be wrapped in double quotes to avoid unintended evaluation.

Useful Patterns

When developing policies for Kubernetes resources, there are several patterns which are common where JMESPath can be useful. This section attempts to capture example patterns that have been observed through real world use cases and how to write JMESPath for them.

Flattening Arrays

In many Kubernetes resources, arrays of both objects and strings are very common. For example, in Pod resources, spec.containers[] is an array of objects where each object in the array may optionally specify args[] which is an array of strings. Policy very often must be able to peer into these arrays and match a given pattern with enough flexibility to implement a sufficiently advanced level of control. The JMESPath flatten operator can help to simplify these checks so writing Kyverno policy becomes less verbose and require fewer rules.

Pods may contain multiple containers and in different locations in the Pod spec tree, for example ephemeralContainers[], initContainers[], and containers[]. Regardless of where the container occurs, a container is still a container. And although it’s possible to name each location in the spec individually, this produces rule or expression sprawl. It is often more efficient to collect all the containers together in a single query for processing. Consider the example Pod below.

 1apiVersion: v1
 2kind: Pod
 3metadata:
 4  name: mypod
 5spec:
 6  initContainers:
 7  - name: redis
 8    image: redis
 9  containers:
10  - name: busybox
11    image: busybox
12  - name: nginx
13    image: nginx

Assume this Pod is saved as pod.yaml locally, its containers[] may be queried using a simple JMESPath expression after using yq to output it as a JSON document then piped into the Kyverno CLI.

 1$ yq e pod.yaml -o json | kyverno jp "spec.containers[]"
 2[
 3  {
 4    "image": "busybox",
 5    "name": "busybox"
 6  },
 7  {
 8    "image": "nginx",
 9    "name": "nginx"
10  }
11]

The above output shows the return of an array of objects as expected where each object is the container. But by using a multi-select list, the initContainer[] array may also be parsed.

 1$ yq e pod.yaml -o json | kyverno jp "spec.[initContainers, containers]"
 2[
 3  [
 4    {
 5      "image": "redis",
 6      "name": "redis"
 7    }
 8  ],
 9  [
10    {
11      "image": "busybox",
12      "name": "busybox"
13    },
14    {
15      "image": "nginx",
16      "name": "nginx"
17    }
18  ]
19]

In the above, a multi-select list spec.[initContainers, containers] “wraps” the results of both initContainers[] and containers[] in parent array thereby producing an array consisting of multiple arrays. By using the flatten operator, these results can be collapsed into just a single array.

 1$ yq e pod.yaml -o json | kyverno jp "spec.[initContainers, containers][]"
 2[
 3  {
 4    "image": "redis",
 5    "name": "redis"
 6  },
 7  {
 8    "image": "busybox",
 9    "name": "busybox"
10  },
11  {
12    "image": "nginx",
13    "name": "nginx"
14  }
15]

With just a single array in which all containers, regardless of where they are, occur in a single hierarchy, it becomes easier to process the data for relevant fields and take action. For example, if you wished to write a policy which forbid using the image named busybox in a Pod, by flattening all containers it becomes easier to isolate just the image field. Because it does not matter where busybox may be found, if found the entire Pod must be rejected. Therefore, while loops or other methods may work, a more efficient method is to simply gather all containers across the Pod and flatten them.

1$ yq e pod.yaml -o json | kyverno jp "spec.[initContainers, containers][].image"
2[
3  "redis",
4  "busybox",
5  "nginx"
6]

With all of the images stored in a simple array, the values can be parsed much easier and just one expression written to contain the necessary logic.

1deny:
2  conditions:
3    any:
4    - key: busybox
5      operator: AnyIn
6      value: "{{request.object.spec.[initContainers, containers][].image}}"

Non-Existence Checks

It is common for a JMESPath expression to name a specific field so that its value may be acted upon. For example, in the basics section above, the label appns is written to a Pod via a mutate rule which does not contain it or is set to a different value. A Kyverno validate rule which exists to check the value of that label or any other field is commonplace. Because the schema for many Kubernetes resources is flexible in that many fields are optional, policy rules must contend with the scenario in which a matching resource does not contain the field being checked. When using JMESPath to check the value of such a field, a simple expression might be written {{request.object.metadata.labels.appns}}. If a resource is submitted which either does not contain any labels at all or does not contain a label with the specified key then the expression cannot be evaluated. An error is likely to result similar to JMESPath query failed: Unknown key "labels" in path. In these types of cases, the JMESPath expression should use a non-existence check in the form of the OR expression followed by a “default” value if the field does not exist. The resulting full expression which will correctly evaluate is {{request.object.metadata.labels.appns || ''}}. This expression reads, “take the value of the key request.object.metadata.labels.appns or, if it does not exist, set it to an empty string”. Note that the value on the right side may need to be customized given the ultimate use of the value expected to be produced. This non-existence pattern can be used in almost any JMESPath expression to mitigate scenarios in which the initial query may be invalid.

Custom Filters

In addition to the filters available in the upstream JMESPath library which Kyverno uses, there are also many new and custom filters developed for Kyverno’s use found nowhere else. These filters augment the already robust capabilities of JMESPath to bring new functionality and capabilities which help solve common use cases in running Kubernetes. The filters endemic to Kyverno can be used in addition to any of those found in the upstream JMESPath library used by Kyverno and do not represent replaced or removed functionality.

For instructions on how to test these filters in a standalone method (i.e., outside of Kyverno policy), see the documentation on the kyverno jp subcommand.

Information on each subcommand, its inputs and output, and specific usage instructions can be found below along with helpful and common use cases that have been identified.

Add

Expand

The add() filter very simply adds two values and produces a sum. The official JMESPath library does not include most basic arithmetic operators such as add, subtract, multiply, and divide, the exception being sum() as documented here. While sum() is useful in that it accepts an array of integers as an input, add() is useful as a simplified filter when only two individual values need to be summed. Note that add() here is different from the length() filter which is used to obtain a count of a certain number of items. Use add() instead when you have values of two fields you wish to add together.

add() is also value-aware (based on the formatting used for the inputs) and is capable of adding numbers, quantities, and durations without any form of unit conversion.

Arithmetic filters like add() currently accept inputs in the following formats.

  • Number (ex., `10`)
  • Quantity (ex., ‘10Mi’)
  • Duration (ex., ‘10h’)

Note that how the inputs are enclosed determines how Kyverno interprets their type. Numbers enclosed in back ticks are scalar values while quantities and durations are enclosed in single quotes thus treating them as strings. Using the correct enclosing character is important because, in Kubernetes “regular” numbers are treated implicitly as units of measure. The number written `10` is interpreted as an integer or “the number ten” whereas ‘10’ is interpreted as a string or “ten bytes”. See the Formatting section above for more details.

Input 1 Input 2 Output
Number Number Number
Quantity or Number Quantity or Number Quantity
Duration or Number Duration or Number Duration

Some specific behaviors to note:
  • If a duration (‘1h’) and a number (`5`) are the inputs, the number will be interpreted as seconds resulting in a sum of 1h0m5s.
  • Because of durations being a string just like resource quantities, and the minutes unit of “m” also present in quantities interpreted as the “milli” prefix, there is no support for minutes.

Example: This policy denies a Pod if any of its containers specify memory requests and limits in excess of 200Mi.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: add-demo
 5spec:
 6  validationFailureAction: enforce
 7  background: false
 8  rules:
 9  - name: add-demo
10    match:
11      any:
12      - resources:
13          kinds:
14          - Pod
15    preconditions:
16      any:
17      - key: "{{ request.operation }}"
18        operator: In
19        value: ["CREATE","UPDATE"]
20    validate:
21      message: "The total memory defined in requests and limits must not exceed 200Mi."
22      foreach:
23      - list: "request.object.spec.containers"
24        deny:
25          conditions:
26            any:
27            - key: "{{ add('{{ element.resources.requests.memory || `0` }}', '{{ element.resources.limits.memory || `0` }}') }}"
28              operator: GreaterThan
29              value: 200Mi

Base64_decode

Expand

The base64_decode() filter takes in a base64-encoded string and produces the decoded output similar to the tool and command base64 --decode. This can be useful when working with Kubernetes Secrets and deciphering their values in order to take action on them in a policy.

Input 1 Output
String String

Some specific behaviors to note:
  • Base64-encoded strings with newline characters will be printed back with them inline.

Example: This policy checks every container, initContainer, and ephemeralContainer in a Pod and decodes a Secret having the path data.license to ensure it does not refer to a prohibited license key.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: base64-decode-demo
 5spec:
 6  background: false
 7  validationFailureAction: enforce
 8  rules:
 9  - name: base64-decode-demo
10    match:
11      any:
12      - resources:
13          kinds:
14          - Pod
15    preconditions:
16      all:
17      - key: "{{ request.object.spec.[containers, initContainers, ephemeralContainers][].env[].valueFrom.secretKeyRef || '' | length(@) }}"
18        operator: GreaterThanOrEquals
19        value: 1
20      - key: "{{request.operation}}"
21        operator: NotEquals
22        value: DELETE
23    validate:
24      message: This license key may not be consumed by a Secret.
25      foreach:
26      - list: "request.object.spec.[containers, initContainers, ephemeralContainers][].env[].valueFrom.secretKeyRef"
27        context:
28        - name: status
29          apiCall:
30            jmesPath: "data.license"
31            urlPath: "/api/v1/namespaces/{{request.namespace}}/secrets/{{element.name}}"
32        deny:
33          conditions:
34            any:
35            - key: "{{ status | base64_decode(@) }}"
36              operator: Equals
37              value: W0247-4RXD3-6TW0F-0FD63-64EFD-38180

Base64_encode

Expand

The base64_encode() filter is the inverse of the base64_decode() filter and takes in a regular, plaintext and unencoded string and produces a base64-encoded output similar to the tool and command base64. This can be useful when working with Kubernetes Secrets by encoding data into the base64 format which is the only acceptable format for Kubernetes Secrets.

Input 1 Output
String String

Example: This policy generates a Secret when a new Namespace is created the contents of which is the value of an annotation named corpkey.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: base64-encode-demo
 5spec:
 6  rules:
 7  - name: gen-supkey
 8    match:
 9      any:
10      - resources:
11          kinds:
12          - Namespace
13    generate:
14      apiVersion: v1
15      kind: Secret
16      name: sup-key
17      namespace: "{{request.object.metadata.name}}"
18      synchronize: false
19      data:
20        data:
21          token: "{{ base64_encode('{{ request.object.metadata.annotations.corpkey }}') }}"

Compare

Expand

The compare() filter is provided as an analog to the inbuilt function to Golang of the same name. It compares two strings lexicographically where the first string is compared against the second. If both strings are equal, the result is 0 (ex., “a” compared to “a”). If the first is in lower lexical order than the second, the result is -1 (ex., “a” compared to “b”). And if the first is in higher order than the second, the result is 1 (ex., “b” compared to “a”). Kyverno also has built-in operators for string comparison where Equals is usually the most common, and in most use cases it is more practical to use the Equals operator in expressions such as preconditions and deny.conditions blocks.

Input 1 Input 2 Output
String String Number

Example: This policy will write a new label called dictionary into a Service putting into order the values of two annotations if the order of the first comes before the second.

 1apiVersion : kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: compare-demo
 5spec:
 6  background: false
 7  rules:
 8  - name: write-dictionary
 9    match:
10      any:
11      - resources:
12          kinds:
13          - Service
14    preconditions:
15      any:
16      - key: "{{ compare('{{request.object.metadata.annotations.foo}}', '{{request.object.metadata.annotations.bar}}') }}"
17        operator: LessThan
18        value: 0
19    mutate:
20      patchStrategicMerge:
21        metadata:
22          labels:
23            dictionary: "{{request.object.metadata.annotations.foo}}-{{request.object.metadata.annotations.bar}}"

Divide

Expand

The divide() filter performs arithmetic divide capabilities between two input fields and produces an output quotient. Like other arithmetic custom filters, it is input aware based on the type passed and, for quantities, allows auto conversion between units of measure. For example, dividing 10Mi (ten mebibytes) by 5Ki (five kibibytes) results in the value 5120 as units are first normalized and then canceled through division. The divide() filter is currently under development to better account for all permutations of input types, however the below table captures the most common and practical use cases.

Arithmetic filters like divide() currently accept inputs in the following formats.

  • Number (ex., `10`)
  • Quantity (ex., ‘10Mi’)
  • Duration (ex., ‘10h’)

Note that how the inputs are enclosed determines how Kyverno interprets their type. Numbers enclosed in back ticks are scalar values while quantities and durations are enclosed in single quotes thus treating them as strings. Using the correct enclosing character is important because, in Kubernetes “regular” numbers are treated implicitly as units of measure. The number written `10` is interpreted as an integer or “the number ten” whereas ‘10’ is interpreted as a string or “ten bytes”. See the Formatting section above for more details.

Input 1 Input 2 Output
Number Number Number
Quantity Number Quantity
Quantity Quantity Number
Duration Number Duration
Duration Duration Number

Example: This policy will check every container in a Pod and ensure that memory limits are no more than 2.5x its requests.

 1apiVersion : kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: enforce-resources-as-ratio
 5spec:
 6  validationFailureAction: audit
 7  rules:
 8  - name: check-memory-requests-limits
 9    match:
10      any:
11      - resources:
12          kinds:
13          - Pod
14    preconditions:
15      any:
16      - key: "{{ request.operation }}"
17        operator: In
18        value:
19        - CREATE
20        - UPDATE
21    validate:
22      message: Limits may not exceed 2.5x the requests.
23      foreach:
24      - list: "request.object.spec.containers"
25        deny:
26          conditions:
27            any:
28              # Set resources.limits.memory equal to zero if not present and resources.requests.memory equal to 1m rather than zero
29              # to avoid undefined division error. No memory request in this case is basically the same as 1m. Kubernetes API server
30              # will automatically set requests=limits if only limits is defined.
31            - key: "{{ divide('{{ element.resources.limits.memory || '0' }}', '{{ element.resources.requests.memory || '1m' }}') }}"
32              operator: GreaterThan
33              value: 2.5

Equal_fold

Expand

The equal_fold() filter is designed to provide text case folding for two sets of strings as inputs. Case folding allows comparing two strings for equivalency where the only differences are letter cases. The return is a boolean (either true or false). For example, comparing “pizza” to “Pizza” results in true because other than title case on “Pizza” the strings are equivalent. Likewise with “pizza” and “pIzZa”. Comparing “pizza” to “APPLE” results in false because even once normalized to the same case, the strings are different.

Input 1 Input 2 Output
String String Boolean

Related filters to equal_fold() are to_upper() and to_lower() which can also be used to normalize text for comparison.


Example: This policy will validate that a ConfigMap with a label named dept and the value of a key under data by the same name have the same case-insensitive value.

 1apiVersion : kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: equal-fold-demo
 5spec:
 6  validationFailureAction: enforce
 7  background: false
 8  rules:
 9  - name: validate-dept-label-data
10    match:
11      any:
12      - resources:
13          kinds:
14          - ConfigMap
15    validate:
16      message: The dept label must equal the data.dept value aside from case.
17      deny:
18        conditions:
19          any:
20          - key: "{{ equal_fold('{{request.object.metadata.labels.dept}}', '{{request.object.data.dept}}') }}"
21            operator: NotEquals
22            value: true

Label_match

Expand

The label_match() filter compares two sets of Kubernetes labels (both key and value) and outputs a boolean response if they are equivalent. This custom filter is useful in that it functions similarly to how the Kubernetes API server associates one resource with another through label selectors. There may be one or multiple labels in each set. Labels may occur in any order. A response of true indicates all the labels (key and value) in the first input are accounted for in the second input. The second input, to which the first is compared, may have additional labels but it must have at minimum all those listed in the first input.

For example, the first collection compared to the second below results in true despite the ordering.

1{
2  "dog": "lab",
3  "color": "tan"
4}
1{
2  "color": "tan",
3  "dog": "lab"  
4}

Likewise, these two below collections also result in true when compared because the entirety of the first is found within the second.

1{
2  "dog": "lab",
3  "color": "tan"
4}
1{
2  "color": "tan",
3  "weight":"chonky",
4  "dog": "lab"  
5}

These last two collections when compared are false because one of the values of one of the labels does not match what is in the first input.

1{
2  "dog": "lab",
3  "color": "tan"
4}
1{
2  "color": "black",
3  "dog": "lab"  
4}
Input 1 Input 2 Output
Map (Object) Map (Object) Boolean

Example: This policy checks all incoming Deployments to ensure they have a matching, preexisting PodDisruptionBudget in the same Namespace. The label_match() filter is used in a query to count how many PDBs have a label set matching that of the incoming Deployment.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: require-pdb
 5spec:
 6  validationFailureAction: audit
 7  background: false
 8  rules:
 9  - name: require-pdb
10    match:
11      any:
12      - resources:
13          kinds:
14          - Deployment
15    preconditions:
16      any:
17      - key: "{{request.operation}}"
18        operator: Equals
19        value: CREATE
20    context:
21    - name: pdb_count
22      apiCall:
23        urlPath: "/apis/policy/v1beta1/namespaces/{{request.namespace}}/poddisruptionbudgets"
24        jmesPath: "items[?label_match(spec.selector.matchLabels, `{{request.object.spec.template.metadata.labels}}`)] | length(@)"
25    validate:
26      message: "There is no corresponding PodDisruptionBudget found for this Deployment."
27      deny:
28        conditions:
29          any:
30          - key: "{{pdb_count}}"
31            operator: LessThan
32            value: 1

Modulo

Expand

The modulo() filter returns the modulo or remainder between a division of two numbers. For example, the modulo of a division between 10 and 3 would be 1 since 3 can be divided into 10 only 3 times (equaling 9) while producing 1 as a remainder.

Arithmetic filters like modulo() currently accept inputs in the following formats.

  • Number (ex., `10`)
  • Quantity (ex., ‘10Mi’)
  • Duration (ex., ‘10h’)

Note that how the inputs are enclosed determines how Kyverno interprets their type. Numbers enclosed in back ticks are scalar values while quantities and durations are enclosed in single quotes thus treating them as strings. Using the correct enclosing character is important because, in Kubernetes “regular” numbers are treated implicitly as units of measure. The number written `10` is interpreted as an integer or “the number ten” whereas ‘10’ is interpreted as a string or “ten bytes”. See the Formatting section above for more details.

Input 1 Input 2 Output
Number Number Number

The inputs list is currently under construction.


Example: This policy checks every container and ensures that memory limits are evenly divisible by its requests.

 1apiVersion : kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: modulo-demo
 5spec:
 6  validationFailureAction: audit
 7  rules:
 8  - name: check-memory-requests-limits
 9    match:
10      any:
11      - resources:
12          kinds:
13          - Pod
14    preconditions:
15      any:
16      - key: "{{ request.operation }}"
17        operator: In
18        value:
19        - CREATE
20        - UPDATE
21    validate:
22      message: Limits must be evenly divisible by the requests.
23      foreach:
24      - list: "request.object.spec.containers"
25        deny:
26          conditions:
27            any:
28              # Set resources.limits.memory equal to zero if not present and resources.requests.memory equal to 1m rather than zero
29              # to avoid undefined division error. No memory request in this case is basically the same as 1m. Kubernetes API server
30              # will automatically set requests=limits if only limits is defined.
31            - key: "{{ modulo('{{ element.resources.limits.memory || '0' }}', '{{ element.resources.requests.memory || '1m' }}') }}"
32              operator: GreaterThan
33              value: 0

Multiply

Expand

The multiply() filter performs standard multiplication on two inputs producing an output product. Like other arithmetic filters, it is input aware and will produce output with appropriate units attached.

Arithmetic filters like multiply() currently accept inputs in the following formats.

  • Number (ex., `10`)
  • Quantity (ex., ‘10Mi’)
  • Duration (ex., ‘10h’)

Note that how the inputs are enclosed determines how Kyverno interprets their type. Numbers enclosed in back ticks are scalar values while quantities and durations are enclosed in single quotes thus treating them as strings. Using the correct enclosing character is important because, in Kubernetes “regular” numbers are treated implicitly as units of measure. The number written `10` is interpreted as an integer or “the number ten” whereas ‘10’ is interpreted as a string or “ten bytes”. See the Formatting section above for more details.

Input 1 Input 2 Output
Number Number Number
Quantity Number Quantity
Duration Number Duration

Due to the commutative property of multiplication, the ordering of inputs (unlike with divide()) is irrelevant.

The inputs list is currently under construction.


Example: This policy sets the replica count for a Deployment to a value of two times the current number of Nodes in a cluster.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: multiply-demo
 5spec:
 6  background: false
 7  rules:
 8    - name: multiply-replicas
 9      match:
10        any:
11        - resources:
12            kinds:
13            - Deployment
14      context:
15        - name: nodecount
16          apiCall:
17            urlPath: "/api/v1/nodes"
18            jmesPath: "items[] | length(@)"
19      mutate:
20        patchStrategicMerge:
21          spec:
22            replicas: "{{ multiply( `{{nodecount}}`,`2`) }}"

Parse_json

Expand

The parse_json() filter takes in a string of any valid encoded JSON and parses it into a fully-formed JSON object. This is useful because it allows Kyverno to access and work with string data that is stored anywhere which accepts strings as if it were “native” JSON data. Primary use cases for this filter include adding anything from snippets to whole documents as the values of labels, annotations, or ConfigMaps which should then be consumed by policy.

Input 1 Output
String Any

Example: This policy uses the parse_json() filter to read a ConfigMap where a specified key contains JSON-encoded data (an array of strings in this case) and sets the supplementalGroups field of a Pod, if not already supplied, to that list.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: parse-json-demo
 5spec:
 6  rules:
 7  - name: parse-supplementalgroups-from-json
 8    match:
 9      any:
10      - resources:
11          kinds:
12          - Pod
13    context:
14    - name: gidsMap
15      configMap:
16        name: user-gids-map
17        namespace: default
18    mutate:
19      patchStrategicMerge:
20        spec:
21          securityContext:
22            +(supplementalGroups): "{{ gidsMap.data.\"{{ request.object.metadata.labels.\"corp.com/service-account\" }}\" | parse_json(@)[*].to_number(@) }}"

The referenced ConfigMap may look similar to the below.

1apiVersion: v1
2kind: ConfigMap
3metadata:
4  name: user-gids-map
5  namespace: default
6data:
7  finance: '["1001","1002"]'

Parse_yaml

Expand

The parse_yaml() filter is the YAML equivalent of the parse_json() filter and takes in a string of any valid YAML document, serializes it into JSON, and parses it so it may be processed by JMESPath. Like parse_json(), this is useful because it allows Kyverno to access and work with string data that is stored anywhere which accepts strings as if it were “native” YAML data. Primary use cases for this filter include adding anything from snippets to whole documents as the values of labels, annotations, or ConfigMaps which should then be consumed by policy.

Input 1 Output
String Any

Example: This policy parses a YAML document as the value of an annotation and uses the filtered value from a JMESPath expression in a variable substitution.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: parse-yaml-demo
 5spec:
 6  validationFailureAction: enforce
 7  background: false
 8  rules:
 9  - name: check-goodbois
10    match:
11      any:
12      - resources:
13          kinds:
14          - Pod
15    validate:
16      message: "Only good bois allowed."
17      deny:
18        conditions:
19        - key: "{{request.object.metadata.annotations.pets | parse_yaml(@).species.isGoodBoi }}"
20          operator: NotEquals
21          value: true

The referenced Pod may look similar to the below.

 1apiVersion: v1
 2kind: Pod
 3metadata:
 4  name: mypod
 5  labels:
 6    app: busybox
 7  annotations:
 8    pets: |-
 9      species:
10        dog: lab
11        name: dory
12        color: black
13        height: 15
14        isGoodBoi: false
15        snacks:
16        - chimken
17        - fries
18        - pizza      
19spec:
20  containers:
21  - name: busybox
22    image: busybox:1.28

Path_canonicalize

Expand

The path_canonicalize() filter is used to normalize or canonicalize a given path by removing excess slashes. For example, a path supplied to the filter may be /var//lib///kubelet which will be canonicalized into /var/lib/kubelet which is how an operating system would interpret the former. This filter is primarily used as a circumvention protection for what would otherwise be strict string matches for paths.

Input 1 Output
String String

Example: This policy uses the path_canonicalize() filter to check the value of each hostPath.path field in a volume block in a Pod to ensure it does not attempt to mount the Containerd host socket.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: path-canonicalize-demo
 5spec:
 6  validationFailureAction: enforce
 7  background: false
 8  rules:
 9  - name: disallow-mount-containerd-sock
10    match:
11      any:
12      - resources:
13          kinds:
14          - Pod
15    validate:
16      foreach:
17      - list: "request.object.spec.volumes[]"
18        deny:
19          conditions:
20            any:
21            - key: "{{ path_canonicalize(element.hostPath.path) }}"
22              operator: Equals
23              value: "/var/run/containerd/containerd.sock"

Pattern_match

Expand

The pattern_match() filter is used to perform a simple, non-regex match by specifying an input pattern and the string or number to which it should be compared. The output is always a boolean response. This filter can be useful when wishing to make simpler comparisons, typically with strings or numbers involved. It avoids many of the complexities of regex while still support wildcards such as * (zero or more characters) and ? (any one character). Note that since Kyverno supports overlay-style patterns and wildcards, use of pattern_match() is typically not needed in these scenarios. This filter is more valuable in dynamic lookup scenarios by using JMESPath variables for one or both inputs as exemplified below.

Input 1 Input 2 Output
String String Boolean
String Number Boolean

Example: This policy uses pattern_match() with dynamic inputs by fetching a pattern stored in a ConfigMap against an incoming Namespace label value.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: pattern-match-demo
 5spec:
 6  validationFailureAction: enforce
 7  background: false
 8  rules:
 9  - match:
10      any:
11      - resources:
12          kinds:
13          - Namespace
14    name: dept-billing-check
15    context:
16    - name: deptbillingcodes
17      configMap:
18        name: deptbillingcodes
19        namespace: default
20    validate:
21      message: The department {{request.object.metadata.labels.dept}} must supply a matching billing code.
22      deny:
23        conditions:
24          any:
25            - key: "{{pattern_match('{{deptbillingcodes.data.{{request.object.metadata.labels.dept}}}}', '{{ request.object.metadata.labels.segbill}}') }}"
26              operator: Equals
27              value: false

The ConfigMap used as the source of the patterns may look like below.

1apiVersion: v1
2kind: ConfigMap
3metadata:
4  name: deptbillingcodes
5data:
6  eng_china: 158-6?-3*
7  eng_india: 158-7?-4*
8  busops: 145-0?-9*
9  finops: 145-1?-5*

And a Namespace upon which the above ClusterPolicy may act can look like below.

1apiVersion: v1
2kind: Namespace
3metadata:
4  name: ind-go
5  labels:
6    dept: eng_india
7    segbill: 158-73-417

Regex_match

Expand

The regex_match() filter is similar to the pattern_match() filter except it accepts standard regular expressions as the comparison format. The first input is the pattern, specified in regex format, while the second is the string compared to the pattern which accepts either string or number. The output is always a boolean response. For example, the following two expressions, which check to ensure a number is in the range of one to seven, both evaluate to true.

regex_match('^[1-7]$',`1`)
regex_match('^[1-7]$','1')
Input 1 Input 2 Output
String String Boolean
String Number Boolean

Example: This policy checks that a PersistentVolumeClaim resource contains an annotation named backup-schedule and its value conforms to a standard Cron expression string. Note that the regular expression in the first input has had an additional backslash added to each backslash to be valid YAML. To use this sample regex in other applications, remove one of each double backslash pair.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: regex-match-demo
 5spec:
 6  background: true
 7  validationFailureAction: enforce
 8  rules:
 9  - name: validate-backup-schedule-annotation-cron
10    match:
11      any:
12      - resources:
13          kinds:
14          - PersistentVolumeClaim
15    validate:
16      message: The annotation `backup-schedule` must be present and in cron format.
17      deny:
18        conditions:
19          any:
20          - key: "{{ regex_match('^((?:\\*|[0-5]?[0-9](?:(?:-[0-5]?[0-9])|(?:,[0-5]?[0-9])+)?)(?:\\/[0-9]+)?)\\s+((?:\\*|(?:1?[0-9]|2[0-3])(?:(?:-(?:1?[0-9]|2[0-3]))|(?:,(?:1?[0-9]|2[0-3]))+)?)(?:\\/[0-9]+)?)\\s+((?:\\*|(?:[1-9]|[1-2][0-9]|3[0-1])(?:(?:-(?:[1-9]|[1-2][0-9]|3[0-1]))|(?:,(?:[1-9]|[1-2][0-9]|3[0-1]))+)?)(?:\\/[0-9]+)?)\\s+((?:\\*|(?:[1-9]|1[0-2])(?:(?:-(?:[1-9]|1[0-2]))|(?:,(?:[1-9]|1[0-2]))+)?)(?:\\/[0-9]+)?)\\s+((?:\\*|[0-7](?:-[0-7]|(?:,[0-7])+)?)(?:\\/[0-9]+)?)$', '{{request.object.metadata.annotations.\"backup-schedule\" || ''}}') }}"
21            operator: Equals
22            value: false

Regex_replace_all

Expand

The regex_replace_all() filter is similar to the replace_all() filter only differing by the first and third inputs being a valid regular expression rather than a static string. For literal replacement, see regex_replace_all_literal(). If numbers are supplied for the second and third inputs, they will internally be converted to string. The output is always a string. For example, the expression regex_replace_all('([0-9])([0-9])', 'hello im 42 months old', '${1}1') results in the output hello im 41 months old. The first input provides the regex which should be used to match against the second input, and the third serves as the replacement which, in this case, replaces the first capture group from the end with the number 1.

Input 1 Input 2 Input 3 Output
Regex (String) String Regex (String) String
Regex (String) Number Number String

Example: This policy mutates a Deployment having label named retention to set the last number to 0. For example, an incoming Deployment with the label value of days_37 would result in the value days_30 after mutation.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: regex-replace-all-demo
 5spec:
 6  background: false
 7  rules:
 8  - name: retention-adjust
 9    match:
10      any:
11      - resources:
12          kinds:
13          - Deployment
14    mutate:
15      patchStrategicMerge:
16        metadata:
17          labels:
18            retention: "{{ regex_replace_all('([0-9])([0-9])', '{{ @ }}', '${1}0') }}"

Regex_replace_all_literal

Expand

The regex_replace_all_literal() filter is similar to the regex_replace_all() filter with the third input being a static string used for literal replacement. If numbers are supplied for the second and third inputs, they will internally be converted to string. The output is always a string. For example, the expression regex_replace_all_literal('^(\d{3}-?\d{2}-?\d{4})$', '123-45-6789', 'redacted') would return redacted as the regex filter matches the faux social security number of 123-45-6789.

Input 1 Input 2 Input 3 Output
Regex (String) String String String
Regex (String) Number Number String

Example: This policy replaces the image registry for each image in every container so it comes from myregistry.corp.com. Note that, for images without an explicit registry such as nginx:latest, Kyverno will internally replace this to be docker.io/nginx:latest and thereby ensuring the regex pattern below matches.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: regex-replace-all-literal-demo
 5spec:
 6  background: false
 7  rules:
 8    - name: replace-image-registry
 9      match:
10        any:
11        - resources:
12            kinds:
13              - Pod
14      mutate:
15        foreach:
16        - list: "request.object.spec.containers"
17          patchStrategicMerge:
18            spec:
19              containers:
20              - name: "{{ element.name }}"
21                image: "{{ regex_replace_all_literal('^[^/]+', '{{element.image}}', 'myregistry.corp.com' )}}"

Replace

Expand

The replace() filter is similar to the replace_all() filter except it takes a fourth input (a number) to specify how many instances of the source string should be replaced with the replacement string in a parent. For example, the expression shown below results in the value Lorem muspi dolor sit amet foo muspi bar ipsum because only two instances of the string ipsum were requested to be replaced. String replacement begins at the left and proceeds to the right halting once the desired count has been reached. If -1 is specified for the four input, it results in all instances of the source string being replaced (effectively the same behavior as replace_all()).

replace('Lorem ipsum dolor sit amet foo ipsum bar ipsum', 'ipsum', 'muspi', `2`)
Input 1 Input 2 Input 3 Input 4 Output
String String String Number String

Example: This policy replaces the rule on an Ingress resource so that the path field will replace the first instance of /cart with /shoppingcart.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: replace-demo
 5spec:
 6  background: false
 7  rules:
 8    - name: replace-path
 9      match:
10        any:
11        - resources:
12            kinds:
13              - Ingress
14      mutate:
15        foreach:
16        - list: "request.object.spec.rules[].http.paths[]"
17          patchStrategicMerge:
18            spec:
19              rules:
20              - http:
21                  paths:
22                  - backend:
23                      service: 
24                        name: kuard
25                        port: 
26                          number: 8080
27                    path: "{{ replace('{{element.path}}', '/cart', '/shoppingcart', `1`) }}"
28                    pathType: ImplementationSpecific

Replace_all

Expand

The replace_all() filter is used to find and replace all instances of one string with another in an overall parent string. Input strings are assumed to be literal and do not support wildcards. For example, the expression replace_all('Lorem ipsum dolor sit amet', 'ipsum', 'muspi') results in the value Lorem muspi dolor sit amet as the string ipsum has been replaced with muspi. If there were multiple instances of ipsum in the parent string, they would all be replaced with muspi.

Input 1 Input 2 Input 3 Output
String String String String

Example: This policy uses replace_all() to replace the string release-name--- with the contents of the annotation meta.helm.sh/release-name in the workingDir field under a container entry within a Deployment.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: replace-all-demo
 5spec:
 6  background: false
 7  rules:
 8    - name: replace-workingdir
 9      match:
10        any:
11        - resources:
12            kinds:
13              - Deployment
14      mutate:
15        patchStrategicMerge:
16          spec:
17            template:
18              spec:
19                containers:
20                  - (name): "*"
21                    workingDir: "{{ replace_all('{{@}}', 'release-name---', '{{request.object.metadata.annotations.\"meta.helm.sh/release-name\"}}') }}"

Semver_compare

Expand

The semver_compare() filter compares two strings which comply with the semantic versioning schema and outputs a boolean response as to the position of the second relative to the first. The first input is the “base” semver string for comparison while the second is the version is compared against the first. The second string accepts an operator prefix and supports AND and OR logic. It also supports the special placeholder variable “x” in any position. For some examples, semver_compare('1.2.3','1.2.4') results in the output false because version 1.2.4 is not equal to version 1.2.3. semver_compare('4.1.3','>=4.1.x') results in the output true because 4.1.3 is greater than or equal to 4.1.x. semver_compare('4.1.3','!4.x.x') returns false because 4.1.3 is equal to 4.x.x. semver_compare('1.8.6','>1.0.0 <2.0.0') returns true because the second input is an AND expression and 1.8.6 is both greater than 1.0.0 and less than 2.0.0. And semver_compare('2.1.5','<2.0.0 || >=3.0.0') returns false because 2.1.5 is neither less than 2.0.0 nor greater than or equal to 3.0.0.

Input 1 Input 2 Output
String String Boolean

Example: This policy uses semver_compare() to check the attestations on a container image and denies it has been built with httpclient greater than version 4.5.0.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: semver-compare-demo
 5spec:
 6  validationFailureAction: enforce
 7  background: false
 8  rules:
 9    - name: check-sbom
10      match:
11        any:
12        - resources:
13            kinds:
14              - Pod
15      verifyImages:
16      - image: "ghcr.io/kyverno/test-verify-image*"
17        key: |-
18          -----BEGIN PUBLIC KEY-----
19          MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHMmDjK65krAyDaGaeyWNzgvIu155
20          JI50B2vezCw8+3CVeE0lJTL5dbL3OP98Za0oAEBJcOxky8Riy/XcmfKZbw==
21          -----END PUBLIC KEY-----          
22        attestations:
23        - predicateType: https://example.com/CycloneDX/v1
24          conditions:
25            - all:
26              - key: "{{ components[?name == 'commons-logging'].version | [0] }}"
27                operator: GreaterThanOrEquals
28                value: "1.2.0"
29              - key: "{{ semver_compare( {{ components[?name == 'httpclient'].version | [0] }}, '>4.5.0') }}"
30                operator: Equals
31                value: true

Split

Expand

The split() filter is used to take in an input string, a character or sequence found within that string, and split the source into an array of strings. For example, the string cat,dog,horse can be split on the comma (,) character resulting in three separate strings in the collection ["cat","dog","horse"]. This filter is often most useful when looping over a number of different strings within a single value and performing some comparison or expression.

Input 1 Input 2 Output
String String Array/string

Example: This policy checks an incoming Ingress to ensure its root path does not conflict with another root path in a different Namespace. It requires that incoming Ingress resources have a single rule with a single path only and assumes the root path is specified explicitly in an existing Ingress rule (ex., when blocking /foo/bar /foo must exist by itself and not part of /foo/baz).

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: split-demo
 5spec:
 6  validationFailureAction: audit
 7  background: false
 8  rules:
 9    - name: check-path
10      match:
11        resources:
12          kinds:
13            - Ingress
14      context:
15        # Looks up the Ingress paths across the whole cluster.
16        - name: allpaths
17          apiCall:
18            urlPath: "/apis/networking.k8s.io/v1/ingresses"
19            jmesPath: "items[].spec.rules[].http.paths[].path"
20        # Looks up the Ingress paths in the same Namespace where the incoming request is targeted.
21        - name: nspath
22          apiCall:
23            urlPath: "/apis/networking.k8s.io/v1/namespaces/{{request.object.metadata.namespace}}/ingresses"
24            jmesPath: "items[].spec.rules[].http.paths[].path"
25      preconditions:
26        - key: "{{request.operation}}"
27          operator: Equals
28          value: "CREATE"
29      validate:
30        message: >-
31          The root path /{{request.object.spec.rules[].http.paths[].path | to_string(@) | split(@, '/') | [1]}}/ exists
32          in another Ingress rule elsewhere in the cluster.          
33        deny:
34          conditions:
35            all:
36              # Deny if the root path of the request exists somewhere else in the cluster other than the same Namespace.
37              - key: /{{request.object.spec.rules[].http.paths[].path | to_string(@) | split(@, '/') | [1]}}/
38                operator: In
39                value: "{{allpaths}}"
40              - key: /{{request.object.spec.rules[].http.paths[].path | to_string(@) | split(@, '/') | [1]}}/
41                operator: NotIn
42                value: "{{nspath}}"

Subtract

Expand

The subtract() filter performs arithmetic subtraction capabilities between two input fields (terms) and produces an output difference. Like other arithmetic custom filters, it is input aware based on the type passed and, for quantities, allows auto conversion between units of measure. For example, subtracting 10Mi (ten mebibytes) minus 5Ki (five kibibytes) results in the value 10235Ki. The subtract() filter is currently under development to better account for all permutations of input types, however the below table captures the most common and practical use cases.

Arithmetic filters like subtract() currently accept inputs in the following formats.

  • Number (ex., `10`)
  • Quantity (ex., ‘10Mi’)
  • Duration (ex., ‘10h’)

Note that how the inputs are enclosed determines how Kyverno interprets their type. Numbers enclosed in back ticks are scalar values while quantities and durations are enclosed in single quotes thus treating them as strings. Using the correct enclosing character is important because, in Kubernetes “regular” numbers are treated implicitly as units of measure. The number written `10` is interpreted as an integer or “the number ten” whereas ‘10’ is interpreted as a string or “ten bytes”. See the Formatting section above for more details.

Input 1 Input 2 Output
Number Number Number
Quantity Quantity Number
Duration Duration Duration

Example: This policy sets the value of a new label called lessreplicas to the value of the current number of replicas in a Deployment minus two so long as there are more than two replicas to start with.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: subtract-demo
 5spec:
 6  background: false
 7  rules:
 8  - name: subtract-demo
 9    match:
10      any:
11      - resources:
12          kinds:
13          - Deployment
14    preconditions:
15      any:
16      - key: "{{ request.object.spec.replicas }}"
17        operator: GreaterThan
18        value: 2
19    mutate:
20      patchStrategicMerge:
21        metadata:
22          labels:
23            lessreplicas: "{{ subtract('{{ request.object.spec.replicas }}',`2`) }}"

Time_since

Expand

The time_since() filter is used to calculate the difference between a start and end period of time where the end may either be a static definition or the then-current time. The time formats currently supported are RFC3339 (the same as used by Kubernetes) or a user-definable time format as supported by the time.Parse() Go function as documented here. The output time difference is always given in hours, minutes, and seconds where seconds may either be an integer or floating point. For example, the expression time_since('','2022-04-10T03:14:05-07:00','2022-04-11T03:14:05-07:00') will result in the output of "24h0m0s". The first input for time format defaults to RFC3339. The expression time_since('Mon Jan _2 15:04:05 MST 2006', 'Mon Jan 02 15:04:05 MST 2021', 'Mon Jan 10 03:14:16 MST 2021') uses Unix date format for the inputs (the same as when running the date program) and will result in the output "180h10m11s". Helm time format is also parsable, for example time_since('2006-Jan-02','2020-Jan-14','2020-Jan-17') resulting in "72h0m0s". And the expression time_since('','2022-04-10T03:14:05-07:00','') will result in the difference between the current time and the second input. The output will be given in which seconds is a floating point value, for example "28h0m33.8257394s".

The time format (layout) parameter is optional and will be defaulted to RFC3339 if left empty (i.e., ‘'). It may not be set explicitly to an RFC3339 format. The time end (third input) may be set to an empty string indicating the current time (i.e., now) when the expression is evaluated.

Input 1 Input 2 Input 3 Output
Time format (String) Time start (String) Time end (String) Time difference (String)

Example: This policy uses time_since() to compare the time a container image was created to the present time, blocking if that difference is greater than six months.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: time-since-demo
 5spec:
 6  validationFailureAction: audit 
 7  rules:
 8    - name: block-stale-images
 9      match:
10        any:
11        - resources:
12            kinds:
13            - Pod
14      validate:
15        message: "Images built more than 6 months ago are prohibited."
16        foreach:
17        - list: "request.object.spec.containers"
18          context:
19          - name: imageData
20            imageRegistry:
21              reference: "{{ element.image }}"
22          deny:
23            conditions:
24              all:
25                - key: "{{ time_since('', '{{ imageData.configData.created }}', '') }}"
26                  operator: GreaterThan
27                  value: 4380h

To_lower

Expand

The to_lower() filter takes in a string and outputs the same string with all lower-case letters. It is the opposite of to_upper().

Input 1 Output
String String

Example: This policy sets the value of a label named zonekey to all caps.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: to-lower-demo
 5spec:
 6  rules:
 7  - name: format-deploy-zone
 8    match:
 9      any:
10      - resources:
11          kinds:
12          - Service
13    mutate:
14      patchStrategicMerge:
15        metadata:
16          labels:
17            zonekey: "{{ to_lower('{{@}}') }}"

To_upper

Expand

The to_upper() filter takes in a string and outputs the same string with all upper-case letters. It is the opposite of to_lower().

Input 1 Output
String String

Example: This policy sets the value of a label named deployzone to all caps.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: to-upper-demo
 5spec:
 6  rules:
 7  - name: format-deploy-zone
 8    match:
 9      any:
10      - resources:
11          kinds:
12          - Service
13    mutate:
14      patchStrategicMerge:
15        metadata:
16          labels:
17            deployzone: "{{ to_upper('{{@}}') }}"

Trim

Expand

The trim() filter takes a string containing a “source” string, a second string representing a collection of discrete characters, and outputs the remainder of the source when both ends of the source string are trimmed by characters appearing in the collection. For example, inputs of ¡¡¡Hello, Gophers!!! and will result in the output Hello, Gophers since the characters ¡ and ! are found at the beginning and end of the input string and will be trimmed. In the case of trim('foocorpcom','mo') the output returned is foocorpc since letters m and o are found at the end of foocorpcom. Notice that ordering of the letters in the second input is irrelevant. Interior characters will not be stripped unless exterior characters have also been removed. For example, trim('foocorpcom','o') will return the input of foocorpcom because o does not occur at the beginning or end of the input string. Characters named in the second input will be deduplicated from the source string so long as outside characters have been trimmed first. For example, trim('foocorpcom','mcof') will result in the output of rp since the other four characters in the second input collection can be stripped from the beginning and end of the input string.

This filter is similar to truncate(). The trim() filter can be useful to remove exact portions of a string when they are known literally.

Input 1 Input 2 Output
String String String

Example: This policy uses the trim() filter to remove the domain from an email value set in an annotation.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: trim-demo
 5spec:
 6  rules:
 7  - name: trim-extnameemail
 8    match:
 9      any:
10      - resources:
11          kinds:
12          - Service
13    mutate:
14      patchStrategicMerge:
15        metadata:
16          annotations:
17            extnameemail: "{{ trim('{{@}}','@corp.com') }}"

Truncate

Expand

The truncate() filter takes a string, a number, and shortens (truncates) that string from the beginning to only include the desired number of characters. For example, calling truncate() on the string foobar by the number 3 would result in the output of foo because only three character positions were requested. This can be a useful filter when formulating values of names, labels, annotations, or other pieces of metadata to conform to a given length and to avoid overruns.

Input 1 Input 2 Output
String Number String

Example: This policy truncates the value of a label called buildhash to only take the first twelve characters.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: truncate-demo
 5spec:
 6  rules:
 7  - name: truncate-buildhash
 8    match:
 9      any:
10      - resources:
11          kinds:
12          - Namespace
13    mutate:
14      patchStrategicMerge:
15        metadata:
16          labels:
17            buildhash: "{{ truncate('{{@}}',`12`) }}"

Last modified April 18, 2022 at 1:08 PM PST: JMESPath updates (#504) (66dd6c1)