Chapter 4: Using functions
fn eval
and fn render
to execute functions. In this chapter, we will take a closer look at how to execute
functions using these two approaches.Declarative function execution #
In many real-world scenarios, it’s not sufficient to only have packages of static, fully-rendered resource configuration. You want the package to declare both static data as well as operations that should be performed on current resources and any resource that may be added in the future as you edit the package. Example use cases:
- Set the namespace on all namespace-scoped resources
- Always perform schema validation on resources in the package
- Always enforce a constraint policy on resources in the package
- Generate resources using a human-authored custom resource
In kpt, this is achieved by declaring a pipeline of functions in the Kptfile
and executing all the pipelines in the package hierarchy in a depth-first order
using the fn render
command.
In our wordpress example, the top-level wordpress
package declares this
pipeline:
# wordpress/Kptfile (Excerpt)
apiVersion: kpt.dev/v1
kind: Kptfile
metadata:
name: wordpress
pipeline:
mutators:
- image: gcr.io/kpt-fn/set-labels:v0.1
configMap:
app: wordpress
validators:
- image: gcr.io/kpt-fn/kubeval:v0.1
This declares two functions:
set-label
is a mutator function which adds a set of labels to resources.kubeval
is a validator function which validates the resources against their OpenAPI schema.
?> Refer to the Functions Catalog for details on how to use a particular function.
There are two differences between mutators and validators:
- Validators are not allowed to modify resources.
- Validators are always executed after mutators.
The mysql
subpackage declares only a mutator function:
# wordpress/mysql/Kptfile
apiVersion: kpt.dev/v1
kind: Kptfile
metadata:
name: mysql
pipeline:
mutators:
- image: gcr.io/kpt-fn/set-labels:v0.1
configMap:
tier: mysql
Now, let’s render the package hierarchy:
$ kpt fn render wordpress
Package "wordpress/mysql":
[PASS] "gcr.io/kpt-fn/set-labels:v0.1"
Package "wordpress":
[PASS] "gcr.io/kpt-fn/set-labels:v0.1"
[PASS] "gcr.io/kpt-fn/kubeval:v0.1"
Successfully executed 3 function(s) in 2 package(s).
Refer to the render command reference for usage.
When you invoke the render
command, kpt performs the following steps:
Sequentially executes the list of mutators declared in the
mysql
package. The input to the first function is the set of resources read from the configuration files in themysql
package. The output of the first function is the input of the second function and so on.Similarly, executes all the validators declared in the
mysql
package. The input to the first validator is the output of the last mutator. The output of the last validator is the output of the pipeline in themysql
package.Sequentially executes the list of mutators declared in the
wordpress
package. The input to the first function is the union of:- Resources read from configuration files in the
wordpress
package AND - Output of the pipeline from the
mysql
package (Step 2).
- Resources read from configuration files in the
Similarly, execute all the validators declared in the
wordpress
package. The output of the last validator is the output of the pipeline in thewordpress
package.Write the output of step 4 by modifying the local filesystem in-place. This can change both
wordpress
andmysql
packages.
The end result is that:
- Resources in the
mysql
package are labelled withtier: mysql
. - Resources in
mysql
andwordpress
packages are labelled withapp: wordpress
. - Resources in
mysql
andwordpress
packages are validated against their OpenAPI spec.
If any of the functions in the pipeline fails for whatever reason, then the entire pipeline is aborted and the local filesystem is left intact.
Specifying <code>function</code> #
<code>image</code> #
The image
field specifies the container image for the function. You can specify
an image from any container registry. If the registry is omitted, the default
container registry for functions catalog (gcr.io/kpt-fn
) is prepended automatically.
For example, set-labels:v0.1
is automatically expanded to gcr.io/kpt-fn/set-labels:v0.1
.
<code>exec</code> #
The exec
field specifies the executable command for the function. You can specify
an executable with arguments.
Example below uses sed
executable to replace all occurrences of foo
with bar
in the package resources.
# PKG_DIR/Kptfile (Excerpt)
apiVersion: kpt.dev/v1
kind: Kptfile
metadata:
name: app
pipeline:
mutators:
- exec: "sed -e 's/foo/bar/'"
Note that you must render the package by allowing executables by specifying --allow-exec
command line flag as shown below.
$ kpt fn render [PKG_DIR] --allow-exec
Using exec
is not recommended for two reasons:
- It makes the package non-portable since rendering the package requires the executables to be present on the system.
- Executing binaries is not very secure since they can perform privileged operations on the system.
Specifying <code>functionConfig</code> #
In Chapter 2, we saw this conceptual representation of a function invocation:
functionConfig
is an optional meta resource containing the arguments to a
particular invocation of the function. There are two different ways to declare
the functionConfig
.
<code>configPath</code> #
The general way to provide a functionConfig
of arbitrary kind (core or custom
resources), is to declare the resource in a separate file in the same directory
as the Kptfile
and refer to it using the configPath
field.
For example:
# wordpress/mysql/Kptfile
apiVersion: kpt.dev/v1
kind: Kptfile
metadata:
name: mysql
pipeline:
mutators:
- image: set-labels:v0.1
configPath: labels.yaml
# wordpress/mysql/labels.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: labels
data:
tier: mysql
<code>configMap</code> #
Many functions take a functionConfig
of kind ConfigMap
since they only need
simple key/value pairs as argument. For convenience, there is a way to inline
the key/value pairs in the Kptfile
.
The following is equivalent to what we showed before:
# wordpress/mysql/Kptfile
apiVersion: kpt.dev/v1
kind: Kptfile
metadata:
name: mysql
pipeline:
mutators:
- image: set-labels:v0.1
configMap:
tier: mysql
Specifying function <code>name</code> #
Functions can optionally be named using the pipeline.mutators.name
or pipeline.validators.name
field to identify a function.
For example:
# wordpress/mysql/Kptfile
apiVersion: kpt.dev/v1
kind: Kptfile
metadata:
name: mysql
pipeline:
mutators:
- name: set tier label
image: set-labels:v0.1
configMap:
tier: mysql
Unique function names for all functions in the Kptfile function
pipeline is recommended. If name
is specified, kpt pkg update
will merge each function pipeline list as an associative list, using
name
as the merge key. An unspecified name
or duplicated names may
result in unexpected merges.
Specifying <code>selectors</code> #
In some cases, you want to invoke the function only on a subset of resources based on a selection criteria. This can be accomplished using selectors. At a high level, selectors work as follows:
Resources that are selected are passed as input to the function. Resources that are not selected are passed through unchanged.
For example, let’s add a function to the pipeline that adds an annotation to
resources with name wordpress-mysql
only:
# wordpress/Kptfile (Excerpt)
apiVersion: kpt.dev/v1
kind: Kptfile
metadata:
name: wordpress
pipeline:
mutators:
- image: gcr.io/kpt-fn/set-annotations:v0.1
configMap:
tier: mysql
selectors:
- name: wordpress-mysql
- image: gcr.io/kpt-fn/set-labels:v0.1
configMap:
app: wordpress
validators:
- image: gcr.io/kpt-fn/kubeval:v0.1
When you invoke the render command, the mysql
package is rendered first, and set-annotations
function is invoked only on the resources with name wordpress-mysql
. Then, set-label
function is invoked on all the resources in the package hierarchy of wordpress
package.
$ kpt fn render wordpress
Package "wordpress/mysql":
[RUNNING] "gcr.io/kpt-fn/set-label:v0.1"
[PASS] "gcr.io/kpt-fn/set-label:v0.1"
Package "wordpress":
[RUNNING] "gcr.io/kpt-fn/set-annotations:v0.1" on 3 resource(s)
[PASS] "gcr.io/kpt-fn/set-annotations:v0.1"
[RUNNING] "gcr.io/kpt-fn/set-label:v0.1"
[PASS] "gcr.io/kpt-fn/set-label:v0.1"
[RUNNING] "gcr.io/kpt-fn/kubeval:v0.1"
[PASS] "gcr.io/kpt-fn/kubeval:v0.1"
Successfully executed 4 function(s) in 2 package(s).
As another example, let’s add another function to the pipeline that adds a prefix to the name of a resource if:
- it has kind
Deployment
AND namewordpress
OR - it has kind
Service
AND namewordpress
# wordpress/Kptfile (Excerpt)
apiVersion: kpt.dev/v1
kind: Kptfile
metadata:
name: wordpress
pipeline:
mutators:
- image: gcr.io/kpt-fn/set-annotations:v0.1
configMap:
tier: mysql
selectors:
- name: wordpress-mysql
- image: gcr.io/kpt-fn/set-labels:v0.1
configMap:
app: wordpress
- image: gcr.io/kpt-fn/ensure-name-substring:v0.1
configMap:
prepend: dev-
selectors:
- kind: Deployment
name: wordpress
- kind: Service
name: wordpress
validators:
- image: gcr.io/kpt-fn/kubeval:v0.1
Now, let’s render the package:
kpt fn render wordpress
Package "wordpress/mysql":
[RUNNING] "gcr.io/kpt-fn/set-label:v0.1"
[PASS] "gcr.io/kpt-fn/set-label:v0.1"
Package "wordpress":
[RUNNING] "gcr.io/kpt-fn/set-annotations:v0.1" on 3 resource(s)
[PASS] "gcr.io/kpt-fn/set-annotations:v0.1"
[RUNNING] "gcr.io/kpt-fn/set-label:v0.1"
[PASS] "gcr.io/kpt-fn/set-label:v0.1"
[RUNNING] "gcr.io/kpt-fn/ensure-name-substring:v0.1" on 2 resource(s)
[PASS] "gcr.io/kpt-fn/ensure-name-substring:v0.1"
[RUNNING] "gcr.io/kpt-fn/kubeval:v0.1"
[PASS] "gcr.io/kpt-fn/kubeval:v0.1"
Successfully executed 5 function(s) in 2 package(s).
Note that the ensure-name-substring
function is applied only to the
resources matching the selection criteria.
If you have resources with particular labels or annotations that you want to use to
select your resources, you can do so. For example, here is a function that will only
be applied to resources matching the label foo: bar
:
apiVersion: kpt.dev/v1
kind: Kptfile
metadata:
name: wordpress
pipeline:
mutators:
- image: gcr.io/kpt-fn/set-annotations:v0.1
configMap:
tier: mysql
selectors:
- labels:
foo: bar
validators:
- image: gcr.io/kpt-fn/kubeval:v0.1
The following are the matchers you can specify in a selector:
apiVersion
:apiVersion
field value of resources to be selected.kind
:kind
field value of resources to be selected.name
:metadata.name
field value of resources to be selected.namespace
:metadata.namespace
field of resources to be selected.annotations
: resources with matching annotations will be selected.labels
: resources with matching labels will be selected.
Specifying exclusions #
Similar to selectors
, you can also specify resources that should be excluded from functions.
For example, you can exclude a resource if it has both kind “Deployment” and name “nginx”:
apiVersion: kpt.dev/v1
kind: Kptfile
metadata:
name: wordpress
pipeline:
mutators:
- image: gcr.io/kpt-fn/set-annotations:v0.1
configMap:
tier: mysql
exclude:
- kind: Deployment
name: nginx
validators:
- image: gcr.io/kpt-fn/kubeval:v0.1
This is distinct from the following, which excludes a resource if it has either kind “Deployment” or name “nginx”:
apiVersion: kpt.dev/v1
kind: Kptfile
metadata:
name: wordpress
pipeline:
mutators:
- image: gcr.io/kpt-fn/set-annotations:v0.1
configMap:
tier: mysql
exclude:
- kind: Deployment
- name: nginx
validators:
- image: gcr.io/kpt-fn/kubeval:v0.1
The following are the matchers you can specify in an exclusion:
apiVersion
:apiVersion
field value of resources to be excluded.kind
:kind
field value of resources to be excluded.name
:metadata.name
field value of resources to be excluded.namespace
:metadata.namespace
field of resources to be excluded.annotations
: resources with matching annotations will be excluded.labels
: resources with matching labels will be excluded.
Imperative function execution #
The fn eval
command enables you to execute a single function without declaring
it in the package. This is referred to as imperative function execution.
For example, to set the namespace of all resources in the wordpress package hierarchy:
$ kpt fn eval wordpress --image gcr.io/kpt-fn/set-namespace:v0.1 -- namespace=mywordpress
Alternatively, for convenience, you can use the short-hand form of the above command:
$ kpt fn eval wordpress -i set-namespace:v0.1 -- namespace=mywordpress
Refer to the eval command reference for usage.
This changes the resources in the wordpress
package and the mysql
subpackage.
For comparison, this has the same effect as the following declaration:
# wordpress/Kptfile (Excerpt)
apiVersion: kpt.dev/v1
kind: Kptfile
metadata:
name: wordpress
pipeline:
mutators:
- image: gcr.io/kpt-fn/set-namespace:v0.1
configMap:
namespace: mywordpress
So when should you execute a function using eval
instead of render
?
When you have one of these use cases:
- Perform a one-time operation
- Execute a function from a CI/CD system on packages authored by other teams
- Develop shell scripts and chain functions with the Unix pipe (
|
) - Execute the function with privilege (Not allowed by
render
)
We will cover these topics in detail.
Specifying <code>functionConfig</code> #
There are two ways to specify the functionConfig
.
<code>fn-config</code> flag #
The general way to provide a functionConfig
of arbitrary kind (core or custom
resources), is to declare the resource in a separate file and use the
fn-config
flag.
$ cat << EOF > /tmp/fn-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: ns
data:
namespace: mywordpress
EOF
$ kpt fn eval wordpress -i set-namespace:v0.1 --fn-config /tmp/fn-config.yaml
CLI arguments #
Many functions take a functionConfig
of kind ConfigMap
since they only need
simple key/value pairs as argument. For convenience, there is a way to provide
the key/value pairs as command line arguments. The following is equivalent to
what we showed previously:
$ kpt fn eval wordpress -i set-namespace:v0.1 -- namespace=mywordpress
Note that the arguments must come after the separator --
.
Specifying <code>selectors</code> #
Selectors can be used to target specific resources for a function execution.
For example, you can selectively add an annotation to the resources if it has kind
Deployment
AND name wordpress
:
$ kpt fn eval wordpress -i set-annotations:v0.1 --match-kind Deployment --match-name wordpress -- foo=bar
Here is the list of available selector matcher flags:
match-api-version
match-kind
match-name
match-namespace
match-annotations
match-labels
Specifying <code>exclusions</code> #
Exclusions can be used to exclude specific resources for a function execution.
For example, you can set the namespace of all resources in the wordpress package,
except for the ones with the label foo: bar
:
$ kpt fn eval wordpress -i set-namespace:v0.1 --exclude-labels foo=bar -- namespace=my-namespace
If you use multiple exclusions, it will exclude resources that match all provided exclusions. For example, you can set the namespace of all resources, except for those that have both kind “Deployment” and name “nginx”:
$ kpt fn eval wordpress -i set-namespace:v0.1 --exclude-kind Deployment --exclude-name nginx -- namespace=my-namespace
Here is the list of available exclusion flags:
exclude-api-version
exclude-kind
exclude-name
exclude-namespace
exclude-annotations
exclude-labels
Privileged Execution #
Since the function is provided explicitly by the user, eval
can be more
privileged and low-level than a declarative invocation using render
. For
example, it can have access to the host system.
In general, we recommend against having functions that require privileged access
to the host since they can only be executed imperatively and pose a challenge in
terms of security, correctness, portability and speed. If at all possible,
functions should be executed hermetically with all required dependencies either
passed in as KRM resources (input items or functionConfig) or included in the
container image. However, there are some legitimate use cases where the only
available option requires either network access or mounting a volume from the
host. In those situations, you can use eval
as described below.
Network Access #
By default, functions cannot access the network. You can enable network access
using the --network
flag.
For example, kubeval
function can download a JSON schema file:
$ kpt fn eval wordpress -i kubeval:v0.1 --network -- schema_location="https://kubernetesjsonschema.dev"
Mounting Directories #
By default, functions cannot access the host file system. You can use the
--mount
flag to mount host volumes. kpt accepts the same options to --mount
specified on the
Docker Volumes page.
For example, kubeval
function can consume a JSON schema file:
$ kpt fn eval -i kubeval:v0.1 --mount type=bind,src="/path/to/schema-dir",dst=/schema-dir --as-current-user wordpress -- schema_location=file:///schema-dir
Note that the --as-current-user
flag may be required to run the function as
your uid instead of the default nobody
to access the host filesystem.
All volumes are mounted readonly by default. Specify rw=true
to mount volumes
in read-write mode.
--mount type=bind,src="/path/to/schema-dir",dst=/schema-dir,rw=true
Chaining functions using the Unix pipe #
As an alternative to declaring a pipeline in the Kptfile
, you can chain
functions using the Unix pipe.
Here is an example:
$ kpt fn source wordpress \
| kpt fn eval - -i set-namespace:v0.1 -- namespace=mywordpress \
| kpt fn eval - -i set-labels:v0.1 -- app=wordpress env=prod \
| kpt fn sink my-wordpress
Refer to the command reference for usage of source and sink commands.
The following describes the above pipeline:
- The
source
command is used to read the resources in the package hierarchy (wordpress
andmysql
packages). The output of thesource
command follows the KRM Function Specification standard, which we are going to look at in chapter 5. - The output of the
source
function is piped into theset-namespace
function.eval
function is instructed to read inputs items from thestdin
using-
. This is the convention used in all commands in kpt that can read fromstdin
. Theset-namespace
function mutates the input items and emits the output items. - The output of the
set-namespace
function is piped intoset-labels
function which adds the given labels to all resources. - The
sink
command writes the output ofset-labels
to the filesystem.
This is a low-level and less abstracted approach to executing functions. You can
instead write the output of the pipeline to a different directory instead of
mutating the directory in-place. You can also pipe to other programs (e.g.
sed
, yq
) that are not functions. Be mindful that the cost of this low-level
flexibility is not having benefits provided by functions: scalability,
reusability, and encapsulation.
Function results #
In kpt, the counterpart to Unix philsophophy of “everything is a file” is “everything is a
Kubernetes resource”. This also extends to the results of executing functions using eval
or
render
. In addition to providing a human-readable terminal output, these commands provide
structured results which can be consumed by other tools. This enables you to build robust UI layers
on top of kpt. For example:
- Create a custom dashboard that shows the results returned by functions
- Annotate a GitHub Pull Request with results returned by a validator function at the granularity of individuals fields
In both render
and eval
, structured results can be enabled using the --results-dir
flag.
For example:
$ kpt fn render wordpress --results-dir /tmp
Package "wordpress/mysql":
[PASS] "gcr.io/kpt-fn/set-labels:v0.1"
Package "wordpress":
[PASS] "gcr.io/kpt-fn/set-labels:v0.1"
[PASS] "gcr.io/kpt-fn/kubeval:v0.1"
Successfully executed 3 function(s) in 2 package(s).
For complete results, see /tmp/results.yaml
The results are provided as resource of kind FunctionResultList
:
# /tmp/results.yaml
apiVersion: kpt.dev/v1
kind: FunctionResultList
metadata:
name: fnresults
exitCode: 0
items:
- image: gcr.io/kpt-fn/set-labels:v0.1
exitCode: 0
- image: gcr.io/kpt-fn/set-labels:v0.1
exitCode: 0
- image: gcr.io/kpt-fn/kubeval:v0.1
exitCode: 0
Let’s see a more interesting result where the kubeval
function catches a validation issue.
For example, change the value of port
field in service.yaml
from 80
to "80"
and
rerun:
$ kpt fn render wordpress --results-dir /tmp
Package "wordpress/mysql":
[PASS] "gcr.io/kpt-fn/set-labels:v0.1"
Package "wordpress":
[PASS] "gcr.io/kpt-fn/set-labels:v0.1"
[FAIL] "gcr.io/kpt-fn/kubeval:v0.1"
Results:
[ERROR] Invalid type. Expected: integer, given: string in object "v1/Service/wordpress" in file "service.yaml" in field "spec.ports.0.port"
Exit code: 1
For complete results, see /tmp/results.yaml
The results resource will now contain failure details:
# /tmp/results.yaml
apiVersion: kpt.dev/v1
kind: FunctionResultList
metadata:
name: fnresults
exitCode: 1
items:
- image: gcr.io/kpt-fn/set-labels:v0.1
exitCode: 0
- image: gcr.io/kpt-fn/set-labels:v0.1
exitCode: 0
- image: gcr.io/kpt-fn/kubeval:v0.1
exitCode: 1
results:
- message: "Invalid type. Expected: integer, given: string"
severity: error
resourceRef:
apiVersion: v1
kind: Service
name: wordpress
field:
path: spec.ports.0.port
file:
path: service.yaml