Skip to main content
Version: 0.11.0

Why GitOps templates enterprise

GitOpsTemplates enables Application Developers to self-service components and services using Weave GitOps. Turning knowledge into a library that can be self-served.

What are GitOps templates?

GitOps templates allows you to template resources in a single definition. Resources in a template can be anything that can be expressed in yaml (K8s, Flux primitives, TF controller, Crossplane, Cluster API).

FAQ

What are GitOps templates? GitOps templates allow you to template resources in a single definition. Resources in a template can be anything that can be expressed in yaml (K8s, Flux primitives, TF controller, Crossplane, Cluster API). Templates are simple YAML files, that can be enriched with Parameters, Variables, Metadata and conditions. They can be rendered to create the resources they contain. For clusters it can be CAPI objects like MachinePool. It can be as well Kustomization (flux) or a TF controller resource.

Ok, what are the restrictions on GitOps templates?

Basically, the only restriction is that the template needs to be valid YAML. Besides that a rendered template can create any kind of resource.

How do they fit today into Weave GitOps?

We have added some metadata markup, which helps us to render the template nicely in the GUI.

The template consumer will be only provided with the required Parameters/Inputs and the guardrails, the template gets rendered and we create a PR. Merging the PR will create all the templated resources.

How can I use GitOps templates?

GitOps Templates were originally introduced enabling self-service in the cluster creation flow. We quickly extended that to terraform, crossplane and Kubernetes resources like RBAC (Roles + Rolebindings). You can have for example a template that provides a running Developer Environment, consisting in a EKS cluster, a RDS Database, and a branch + revision of the current application through a single template.

Organizing Templates

Declare the type of a template by using the weave.works/template-type label. The value of the label is the name of the template type. The template type is used to group templates in the UI.

Recommended template types:

  • application - for application templates
  • cluster - for cluster templates
  • terraform - for Terraform templates
  • pipeline - for Pipeline templates

Enabling/Disabling Template Components

Enable or disable rendering of certain component sections in a template with the use of annotations. This can be done by using the templates.weave.works/COMPONENT-enabled annotation with a boolean value.

Supported components:

  • profiles
  • kustomizations
  • credentials

Example:

annotations:
templates.weave.works/profiles-enabled: "true"
templates.weave.works/kustomizations-enabled: "true"
templates.weave.works/credentials-enabled: "true"

Default profile values

Default and required profiles can be added via the template annotation capi.weave.works/profile-INDEX so that if the profiles section has been enabled on a template you can choose those profiles for the template.

The annotation is added as the following:

annotations:
capi.weave.works/profile-0: '{"name": "NAME", "version": "VERSION", "editable": EDITABLE, "namespace": "NAMESPACE"}'

Where

  • name - is the name of the profile in the default profiles repository
  • version - (optional) will choose the default version
  • namespace - (optional) is the default target namespace for the profile
  • editable - (optional, default=false), allow the user to de-select this profile, making it a default instead of a requirement.

Rendering Templates

Declare the render type indicating the templating language to be used to render the template by setting spec.renderType.

Supported templating languages:

  • envsubst (default) envsubst which is short for environment substitution uses envsubst for rendering, where ${CLUSTER_NAME} style syntax can be used. It is the same templating format that is used by clusterctl.

    Supported Functions

    ExpressionMeaning
    ${var}Value of $var
    ${#var}String length of $var
    ${var^}Uppercase first character of $var
    ${var^^}Uppercase all characters in $var
    ${var,}Lowercase first character of $var
    ${var,,}Lowercase all characters in $var
    ${var:n}Offset $var n characters from start
    ${var:n:len}Offset $var n characters with max length of len
    ${var#pattern}Strip shortest pattern match from start
    ${var##pattern}Strip longest pattern match from start
    ${var%pattern}Strip shortest pattern match from end
    ${var%%pattern}Strip longest pattern match from end
    ${var-default}If $var is not set, evaluate expression as $default
    ${var:-default}If $var is not set or is empty, evaluate expression as $default
    ${var=default}If $var is not set, evaluate expression as $default
    ${var:=default}If $var is not set or is empty, evaluate expression as $default
    ${var/pattern/replacement}Replace as few pattern matches as possible with replacement
    ${var//pattern/replacement}Replace as many pattern matches as possible with replacement
    ${var/#pattern/replacement}Replace pattern match with replacement from $var start
    ${var/%pattern/replacement}Replace pattern match with replacement from $var end
  • templating

    templating uses text/templating for rendering, using go-templating style syntax {{ .params.CLUSTER_NAME }} where params are provided by the .params variable. Template functions can also be used with the syntax {{ .params.CLUSTER_NAME | FUNCTION }}.

    Supported functions (from Sprig library)

    Function TypeFunctions
    String Functionstrim, wrap, randAlpha, plural
    String List FunctionssplitList, sortAlpha
    Integer Math Functionsadd, max, mul
    Integer Slice Functionsuntil, untilStep
    Float Math Functionsaddf, maxf, mulf
    Date Functionsnow, date
    Defaults Functionsdefault, empty, coalesce, fromJson, toJson, toPrettyJson, toRawJson, ternary
    Encoding Functionsb64enc, b64dec
    Lists and List Functionslist, first, uniq
    Dictionaries and Dict Functionsget, set, dict, hasKey, pluck, dig, deepCopy
    Type Conversion Functionsatoi, int64, toString
    Flow Control Functionsfail
    UUID Functionsuuidv4
    Version Comparison Functionssemver, semverCompare
    ReflectiontypeOf, kindIs, typeIsLike

Editing templates

When rendering a template, a templates.weave.works/create-request annotation is added by default to the first resource in the resourcetemplates. It can be added to any other resource by simply adding the annotation in empty form. This annotation holds information about which template generated the resource and the parameter values used as a json string.

If the resource type is one of the following and has this annotation, an Edit resource button will appear in the UI that allows the editing of the resource and re-rendering it:

  • Applications:
    • HelmRelease
    • Kustomization
  • Sources:
    • HelmRepository
    • GitRepository
  • Clusters:
    • GitopsCluster

Example:

spec:
resourcetemplates:
- apiVersion: v1
kind: ConfigMap
metadata:
name: my-configmap
data:
my-key: my-value
- apiVersion: source.toolkit.fluxcd.io/v1beta1
kind: HelmRepository
metadata:
# This annotation will add an `Edit resource` button in the UI for this resource
annotations:
templates.weave.works/create-request: ''
name: nginx
namespace: default

Custom delimiters for renderType: templating

The default delimiters for renderType: templating are {{ and }}. These can be changed by setting the templates.weave.works/delimiters annotation on the profile. For example:

  • templates.weave.works/delimiters: "{{,}}" - default
  • templates.weave.works/delimiters: "${{,}}"
    • Use ${{ and }}, for example ${{ .params.CLUSTER_NAME }}
    • Useful as {{ in yaml is invalid syntax and needs to be quoted. If you need to provide a un-quoted number value like replicas: 3 you should use these delimiters.
    • replicas: {{ .params.REPLICAS }} Invalid yaml
    • replicas: "{{ .params.REPLICAS }}" Valid yaml, incorrect type. The type is a string not a number and will fail validation.
    • replicas: ${{ .params.REPLICAS }} Valid yaml and correct number type.
  • templates.weave.works/delimiters: "<<,>>"
    • Use << and >>, for example << .params.CLUSTER_NAME >>
    • Useful if you are nesting templates and need to differentiate between the delimiters used in the inner and outer templates.

Modifying the rendered resources

The add-common-bases annotation

The templates.weave.works/add-common-bases: "true" annotation can be used to enabled and disable the addition of a "common bases" Kustomization to the list of rendered files. This kustomization will sync a path that is common to all clusters (clusters/bases). Useful to add RBAC and policy that should be applied to all clusters.

The inject-prune-annotation annotation

The templates.weave.works/inject-prune-annotation: "true" annotation can be used to enable and disable the injection of Flux's prune annotation into certain resources.

When enabled we automatically inject a kustomize.toolkit.fluxcd.io/prune: disabled annotation into every resource in the spec.resourcetemplates that is not a cluster.x-k8s.io.Cluster and not a gitops.weave.works.GitopsCluster.

The intention here is stop flux from explicitly deleting subresources of the Cluster like AWSCluster, KubeadmControlPlane, AWSMachineTemplate etc and let the capi-controllers remove them itself.

This is the pattern recommended in the capi-quickstart guide https://cluster-api.sigs.k8s.io/user/quick-start.html#clean-up.

Differences between CAPITemplate and GitOpsTemplate

The only difference between CAPITemplate and GitOpsTemplate is the default value of these two annotations:

Annotationdefault value for CAPITemplatedefault value for GitOpsTemplate
templates.weave.works/add-common-bases"true""false"
templates.weave.works/inject-prune-annotations"true""false"

How to: Add a GitOps Template to create a cluster

GitOps Templates objects need to be wrapped with the GitOpsTemplate custom resource and then loaded into the management cluster.

apiVersion: clustertemplates.weave.works/v1alpha2
kind: GitOpsTemplate
metadata:
name: cluster-template-development
labels:
weave.works/template-type: cluster
spec:
description: This is the std. CAPD template
renderType: templating
params:
- name: CLUSTER_NAME
description: This is used for the cluster naming.
resourcetemplates:
- apiVersion: cluster.x-k8s.io/v1alpha3
kind: Cluster
metadata:
name: "{{ .params.CLUSTER_NAME }}"

Parameters

You can provide additional metadata about the parameters to the templates in the spec.params section.

Required Parameters

Templates use the CLUSTER_NAME to determine the path in gitrepository and RESOURCE_NAME for the file name when we create the pull request.

You must provide:

  • CLUSTER_NAME
  • RESOURCE_NAME

Default path for templates

The default path for a template has a few components:

  • From the params: CLUSTER_NAME or RESOURCE_NAME, required.
  • From the params: NAMESPACE, default: default
  • From values.yaml for the Weave GitOps Enterprise mccp chart: values.config.capi.repositoryPath, default: ./clusters/management/clusters

These are composed to create the path: ${repositoryPath}/${NAMESPACE}/${CLUSTER_OR_RESOURCE_NAME}.yaml

Using the default values and supplying CLUSTER_NAME as my-cluster will result in the path: ./clusters/management/clusters/default/my-cluster.yaml

Parameters metadata - spec.params

  • name: The variable name within the resource templates
  • description: Description of the parameter. This will be rendered in the UI and CLI
  • options: The list of possible values this parameter can be set to.
  • required - Whether the parameter must contain a non-empty value
  • default - Default value of the parameter

Sample:

spec:
params:
- name: PARAM_NAME_1
description: DESC_1
options: [OPTION_1,OPTION_2]
default: OPTION_1
- name: PARAM_NAME_2
description: DESC_1
required: true
default: DEFAULT_2

Loading the template into the cluster

Load templates into the cluster by adding them to your flux managed git repository or by using apply directly with kubectl apply -f capi-template.yaml

Weave GitOps will search for templates in the default namespace. This can be changed by configuring the config.capi.namespace value in the helm chart.

Full CAPD docker template example

This example works with the CAPD provider, see Cluster API Providers.

clusters/management/capi/templates/capd-template.yaml
apiVersion: capi.weave.works/v1alpha1
kind: CAPITemplate
metadata:
name: cluster-template-development
namespace: default
labels:
weave.works/template-type: cluster
spec:
description: A simple CAPD template
params:
- name: CLUSTER_NAME
required: true
description: This is used for the cluster naming.
- name: NAMESPACE
description: Namespace to create the cluster in
- name: KUBERNETES_VERSION
description: Kubernetes version to use for the cluster
options: ["1.19.11", "1.21.1", "1.22.0", "1.23.3"]
- name: CONTROL_PLANE_MACHINE_COUNT
description: Number of control planes
options: ["1", "2", "3"]
- name: WORKER_MACHINE_COUNT
description: Number of worker machines
resourcetemplates:
- apiVersion: gitops.weave.works/v1alpha1
kind: GitopsCluster
metadata:
name: "${CLUSTER_NAME}"
namespace: "${NAMESPACE}"
labels:
weave.works/capi: bootstrap
spec:
capiClusterRef:
name: "${CLUSTER_NAME}"
- apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
metadata:
name: "${CLUSTER_NAME}"
namespace: "${NAMESPACE}"
labels:
cni: calico
spec:
clusterNetwork:
pods:
cidrBlocks:
- 192.168.0.0/16
serviceDomain: cluster.local
services:
cidrBlocks:
- 10.128.0.0/12
controlPlaneRef:
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlane
name: "${CLUSTER_NAME}-control-plane"
namespace: "${NAMESPACE}"
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DockerCluster
name: "${CLUSTER_NAME}"
namespace: "${NAMESPACE}"
- apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DockerCluster
metadata:
name: "${CLUSTER_NAME}"
namespace: "${NAMESPACE}"
- apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DockerMachineTemplate
metadata:
name: "${CLUSTER_NAME}-control-plane"
namespace: "${NAMESPACE}"
spec:
template:
spec:
extraMounts:
- containerPath: /var/run/docker.sock
hostPath: /var/run/docker.sock
- apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlane
metadata:
name: "${CLUSTER_NAME}-control-plane"
namespace: "${NAMESPACE}"
spec:
kubeadmConfigSpec:
clusterConfiguration:
apiServer:
certSANs:
- localhost
- 127.0.0.1
- 0.0.0.0
controllerManager:
extraArgs:
enable-hostpath-provisioner: "true"
initConfiguration:
nodeRegistration:
criSocket: /var/run/containerd/containerd.sock
kubeletExtraArgs:
cgroup-driver: cgroupfs
eviction-hard: nodefs.available<0%,nodefs.inodesFree<0%,imagefs.available<0%
joinConfiguration:
nodeRegistration:
criSocket: /var/run/containerd/containerd.sock
kubeletExtraArgs:
cgroup-driver: cgroupfs
eviction-hard: nodefs.available<0%,nodefs.inodesFree<0%,imagefs.available<0%
machineTemplate:
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DockerMachineTemplate
name: "${CLUSTER_NAME}-control-plane"
namespace: "${NAMESPACE}"
replicas: "${CONTROL_PLANE_MACHINE_COUNT}"
version: "${KUBERNETES_VERSION}"
- apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DockerMachineTemplate
metadata:
name: "${CLUSTER_NAME}-md-0"
namespace: "${NAMESPACE}"
spec:
template:
spec: {}
- apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
kind: KubeadmConfigTemplate
metadata:
name: "${CLUSTER_NAME}-md-0"
namespace: "${NAMESPACE}"
spec:
template:
spec:
joinConfiguration:
nodeRegistration:
kubeletExtraArgs:
cgroup-driver: cgroupfs
eviction-hard: nodefs.available<0%,nodefs.inodesFree<0%,imagefs.available<0%
- apiVersion: cluster.x-k8s.io/v1beta1
kind: MachineDeployment
metadata:
name: "${CLUSTER_NAME}-md-0"
namespace: "${NAMESPACE}"
spec:
clusterName: "${CLUSTER_NAME}"
replicas: "${WORKER_MACHINE_COUNT}"
selector:
matchLabels: null
template:
spec:
bootstrap:
configRef:
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
kind: KubeadmConfigTemplate
name: "${CLUSTER_NAME}-md-0"
namespace: "${NAMESPACE}"
clusterName: "${CLUSTER_NAME}"
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DockerMachineTemplate
name: "${CLUSTER_NAME}-md-0"
namespace: "${NAMESPACE}"
version: "${KUBERNETES_VERSION}"

Updating from v1alpha1 to v1alpha2

resourcetemplates values was changed in v1alpha2. Instead of directly holding a list of Kubernetes resources manifests it is now a list of a new object that has two keys:

  • content: This now holds the resource manifests.
  • path: This allows you to specify which path to render the templates into in the repo instead of rendering them to the default path.

If the user does not wish to specify where to render the templates but still update and keep the old behavior then he will have to move the resource templates list under the content section.