Templating
November 21, 2024 · View on GitHub
Definition
Landscaper uses templating to dynamically generate configurations based on a given data binding for various purposes. An example is the generation of deployitem manifests based on the imports of a blueprint. The Landscaper supports multiple template engines. Depending on the purpose, dedicated value bindings are provided as input for the templating. Templating is executed in so-called 'executions', defining the context for the templating. Landscaper uses executions for:
deployExecutionsfor rendering deployitemsexportExecutionsfor rendering exportssubinstallationExecutionsfor rendering nested installations
For each of these purposes, a list of executions can be specified. Every execution can use a different template engine. The results of all specified executions with the same purpose will be merged.
For detailed information of blueprints see the Blueprint Docs.
Template Execution
Landscaper uses templating for various purposes, most prominently in the blueprint (e.g. for deployitem and export generation). The dedicated section in the respective manifests is always a list of template execution configurations. Each execution is defined by a set of attributes:
-
namestring The name is used for providing error messages during the templating execution. It is also used as an identifier for the state of the execution. -
typestring The type specifies which template engine should be used. Currently supported types areGoTemplateandSpiff. -
filestring [optional] If this property is set, the template is read from the specified file of the blueprint file structure. Exactly one offileandtemplatehas to be specified. -
templatetemplate [optional] If this property is set, the template is read from the given inline data, according to the specification of the specified template engine type. Exactly one offileandtemplatehas to be specified.
The the rendered output of the templating must always be a YAML document. The document is expected to be a map. The structure is the same, independent of which template engine is used. The expected result is always read from a dedicated key, depending on the execution (e.g. deployItems for deployitem executions).
Example
deployExecutions:
- name: my-spiff-template
type: Spiff
template:
deployItems:
- name: my-first-deploy-item
type: landscaper.gardener.cloud/mock
config: ...
- name: my-go-template
type: GoTemplate
template: |
deployitems:
- name: my-second-deploy-item
type: landscaper.gardener.cloud/mock
config: ...
Filesystem
The blueprint's filesystem structure is accessible for the template engines as root file system.
Depending on what is being rendered, there is more data available during the templating, such as imports, etc.. The available bindings are described in the docs belonging to the resource the templates are part of, e.g. here for the templates in blueprints.
Example
- Filesystem
my-blueprint ├── data │ ├── template │ └── config └── blueprint.yaml - Execution snippet from blueprint.yaml
- name: my-go-template type: GoTemplate file: "data/template" - Template file
deployitems: - name: my-second-deploy-item type: landscaper.gardener.cloud/mock config: {{ include "data/config" . | indent 6 }}
State Handling
Depending on the purpose of the execution, Landscaper supports state handling. An execution can provide information that should be kept among multiple evaluations of the execution (e.g. when the installation is updated). The mechanism, how the state is past to and read from an execution depends on its template engine.
Template Engines
The Landscaper currently supports two template engines:
GoTemplateGo Template enhanced with sprig functions.SpiffSpiff++ templating.
Regardless of the chosen engine, the output is always expected to have the same structure.
:warning: Note that OS functions are not available for security reasons.
Go Template
The execution type to use for go templates is GoTemplate. As go templates are not valid YAML, they have to be provided as a string. Because this is typically a multi-line string, the | notation is mostly used.
Example
- name: my-go-template
type: GoTemplate
template: |
deployitems:
- name: my-second-deploy-item
type: landscaper.gardener.cloud/mock
config: {{ .imports.config }}
Additional Functions
The GoTemplate executor simply is standard go template enhanced with sprig functions.
The following additional functions are available:
-
include(path string, binding interface{}): stringreads and executes a template from the given file with the provided binding (similar to helm's 'include') -
readFile(path string): []bytereads a file from the blueprints filesystem -
readDir(path string): []FileInforeturns all files and directories in the given directory of the blueprint's filesystem. -
toYaml(interface{}): stringconverts the given object to valid yaml. -
fromYaml(string): interface{}unmarshals a given yaml string into an object. -
getResource(ComponentDescriptor, keyValuePairs ...string): GlobalIdentitysearches a resource in the given component descriptors that matches the specified selector. The selector are key-value pairs that describe the resource's identity. e.g.getResource .cd "name" "myResource"-> returns the resource with the namemyResource -
getResourceKey(ComponentDescriptor, reference ...string): Resource: Resolves a resource reference.referenceis either a relative artifact reference in the format described by ocm or a file-path like expression likecd://componentReferences/referenceName1/componentReferences/referenceName2/resources/resourceName1. It constructs the global identity of the resource and returns a base64 encoded string representation of that global identity. This base64 encoded string acts as key which can be used by the deployers to fetch the resource content. -
getComponent(componentDescriptor, keyValuePairs ...string): ComponentDescriptorsearches a component in the given component descriptors that matches the specified selector. The selector are key-value pairs that describe the component reference's identity. e.g.getComponent .cd "name" "myComp"-> seraches in the component descriptor for a component reference with the namemyCompand returns the referenced component descriptor. -
getRepositoryContext(componentDescriptor): RepositoryContextreturns the effective repository context of the given component descriptor -
parseOCIRef(ref string): [2]stringparses an oci reference and returns the repository and the version. e.g.host:5000/myrepo/myimage:1.0.0->["host:5000/myrepo/myimage", "1.0.0"] -
ociRefRepo(ref string): stringparses an oci reference and returns the repository. e.g.host:5000/myrepo/myimage:1.0.0->"host:5000/myrepo/myimage" -
ociRefVersion(ref string): stringparses an oci reference and returns the version. e.g.host:5000/myrepo/myimage:1.0.0->"1.0.0" -
getShootAdminKubeconfig(shootName, shootNamespace string, expirationSeconds int, target Target): string
returns a temporary admin kubeconfig for a Gardener Shoot cluster as described here. The kubeconfig is returned as base64 encoded string.Arguments:
shootNamethe name of the shoot clustershootNamespacethe namespace of the Gardener project to which the Shoot belongs:garden-<PROJECT NAME>expirationSecondsthe number of seconds after which the kubeconfig expirestargeta Target that contains a kubeconfig for the Gardener project to which the Shoot belongs
Here is an example of an export execution that uses the
getShootAdminKubeconfigfunction.export-execution.yaml: | exports: {{- $kubeconfig := getShootAdminKubeconfig .imports.shootName .imports.shootNamespace 86400 .imports.gardenerServiceAccount | b64dec }} shootAdminKubeconfig: | {{- nindent 4 $kubeconfig }} shootAdminTarget: type: landscaper.gardener.cloud/kubernetes-cluster config: kubeconfig: | {{- nindent 8 $kubeconfig }}It constructs two export values:
shootAdminKubeconfigreturns the kubeconfig as string, andshootAdminTargetreturns the kubeconfig wrapped in a Target. You can find the full example here. -
getShootAdminKubeconfigWithExpirationTimestamp(shootName, shootNamespace string, expirationSeconds int, target Target): objectworks likegetShootAdminKubeconfig, but instead of only returning the kubeconfig as string, it returns a struct containing the kubeconfig as well as the expiration timestamp of the token used in it. The returned struct looks like this:kubeconfig: | apiVersion: v1 kind: Config ... expirationTimestamp: 1695369282 # seconds from epoch expirationTimestampReadable: "2023-09-22 09:54:42+02:00" # RFC3339 -
getServiceAccountKubeconfig(serviceAccountName, serviceAccountNamespace string, expirationSeconds int, target Target): stringreturns a kubeconfig for a cluster. The kubeconfig will contain a token obtained by a token request for the specified ServiceAccount. The kubeconfig is returned as base64 encoded string.Arguments:
serviceAccountNamethe name of the ServiceAccountserviceAccountNamespacethe namespace of the ServiceAccountexpirationSecondsthe number of seconds after which the kubeconfig expirestargeta Target that contains a kubeconfig for the cluster
Example:
export-execution.yaml: | exports: {{- $kubeconfig := getServiceAccountKubeconfig .imports.serviceAccountName .imports.serviceAccountNamespace 7776000 .imports.cluster | b64dec }} serviceAccountKubeconfig: | {{- nindent 4 $kubeconfig }} serviceAccountTarget: type: landscaper.gardener.cloud/kubernetes-cluster config: kubeconfig: | {{- nindent 8 $kubeconfig }}You can find the full example here.
-
getServiceAccountKubeconfigWithExpirationTimestamp(serviceAccountName, serviceAccountNamespace string, expirationSeconds int, target Target): objectworks likegetServiceAccountKubeconfig, but instead of only returning the kubeconfig as string, it returns a struct containing the kubeconfig as well as the expiration timestamp of the token used in it. The returned struct looks like this:kubeconfig: | apiVersion: v1 kind: Config ... expirationTimestamp: 1695369282 # seconds from epoch expirationTimestampReadable: "2023-09-22 09:54:42+02:00" # RFC3339
State
Old state is provided via an additional state binding. New state is taken from the state node of the rendered template, if it exists.
Example
- name: my-go-template
type: GoTemplate
template: |
state: {{if .state}}{{add .state 1}}{{else}}1{{end}}
deployitems:
- name: my-second-deploy-item
type: landscaper.gardener.cloud/mock
config: {{ .imports.config }}
Spiff
The execution type to use for spiff templates is Spiff. The template can be provided as either YAML or text.
Example
- name: my-spiff-template
type: Spiff
template:
deployItems:
- name: my-first-deploy-item
type: landscaper.gardener.cloud/mock
config: (( imports.config ))
or
- name: my-spiff-template
type: Spiff
template: |
deployItems:
- name: my-first-deploy-item
type: landscaper.gardener.cloud/mock
config: (( imports.config ))
Additional Functions
-
getResource(ComponentDescriptor, keyValuePairs ...string): Resourcesearches a resource in the given component descriptors that matches the specified selector. The selector are key-value pairs that describe the resource's identity. e.g.getResource .cd "name" "myResource"-> returns the resource with the namemyResource -
getResourceKey(ComponentDescriptor, reference ...string): Resource: Resolves a resource reference.referenceis either a relative artifact reference in the format described by ocm or a file-path like expression likecd://componentReferences/referenceName1/componentReferences/referenceName2/resources/resourceName1. It constructs the global identity of the resource and returns a base64 encoded string representation of that global identity. This base64 encoded string acts as key which can be used by the deployers to fetch the resource content. -
getComponent(componentDescriptor, keyValuePairs ...string): ComponentDescriptorsearches a component in the given component descriptors that matches the specified selector. The selector are key-value pairs that describe the component reference's identity. e.g.getComponent .cd "name" "myComp"-> seraches in the component descriptor for a component reference with the namemyCompand returns the referenced component descriptor. -
parseOCIRef(ref string): [2]stringparses an oci reference and returns the repository and the version. e.g.host:5000/myrepo/myimage:1.0.0->["host:5000/myrepo/myimage", "1.0.0"] -
ociRefRepo(ref string): stringparses an oci reference and returns the repository. e.g.host:5000/myrepo/myimage:1.0.0->"host:5000/myrepo/myimage" -
ociRefVersion(ref string): stringparses an oci reference and returns the version. e.g.host:5000/myrepo/myimage:1.0.0->"1.0.0" -
getShootAdminKubeconfig(shootName, shootNamespace string, expirationSeconds int, target Target): string
returns a temporary admin kubeconfig for a Gardener Shoot cluster as described here. The kubeconfig is returned as base64 encoded string.Arguments:
shootNamethe name of the shoot clustershootNamespacethe namespace of the Gardener project to which the Shoot belongs:garden-<PROJECT NAME>expirationSecondsthe number of seconds after which the kubeconfig expirestargeta Target that contains a kubeconfig for the Gardener project to which the Shoot belongs
Here is an example of an export execution that uses the
getShootAdminKubeconfigfunction.export-execution.yaml: | exports: shootAdminKubeconfig: (( base64(getShootAdminKubeconfig(.imports.shootName, .imports.shootNamespace, 86400, .imports.gardenerServiceAccount)) )) shootAdminTarget: type: landscaper.gardener.cloud/kubernetes-cluster config: kubeconfig: (( shootAdminKubeconfig ))It constructs two export values:
shootAdminKubeconfigreturns the kubeconfig as string, andshootAdminTargetreturns the kubeconfig wrapped in a Target. -
getShootAdminKubeconfigWithExpirationTimestamp(shootName, shootNamespace string, expirationSeconds int, target Target): objectworks likegetShootAdminKubeconfig, but instead of only returning the kubeconfig as string, it returns a struct containing the kubeconfig as well as the expiration timestamp of the token used in it. The returned struct looks like this:kubeconfig: | apiVersion: v1 kind: Config ... expirationTimestamp: 1695369282 # seconds from epoch expirationTimestampReadable: "2023-09-22 09:54:42+02:00" # RFC3339 -
getServiceAccountKubeconfig(serviceAccountName, serviceAccountNamespace string, expirationSeconds int, target Target): stringreturns a kubeconfig for a cluster. The kubeconfig will contain a token obtained by a token request for the specified ServiceAccount. The kubeconfig is returned as base64 encoded string.Arguments:
serviceAccountNamethe name of the ServiceAccountserviceAccountNamespacethe namespace of the ServiceAccountexpirationSecondsthe number of seconds after which the kubeconfig expirestargeta Target that contains a kubeconfig for the cluster
Example:
export-execution.yaml: | exports: serviceAccountKubeconfig: (( base64(getServiceAccountKubeconfig(.imports.serviceAccountName, .imports.serviceAccountNamespace, 7776000, .imports.cluster) )) serviceAccountTarget: type: landscaper.gardener.cloud/kubernetes-cluster config: kubeconfig: (( serviceAccountKubeconfig )) -
getServiceAccountKubeconfigWithExpirationTimestamp(serviceAccountName, serviceAccountNamespace string, expirationSeconds int, target Target): objectworks likegetServiceAccountKubeconfig, but instead of only returning the kubeconfig as string, it returns a struct containing the kubeconfig as well as the expiration timestamp of the token used in it. The returned struct looks like this:kubeconfig: | apiVersion: v1 kind: Config ... expirationTimestamp: 1695369282 # seconds from epoch expirationTimestampReadable: "2023-09-22 09:54:42+02:00" # RFC3339
State
Spiff already has state handling implemented, see here for details.