Skip to content

Latest commit

 

History

History

docs

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 

Templating from Generators

Basics

Currently rendering templates operates in two phases:

  • Generate all template parameters from the configured generators
  • Render all the templates for each set of template parameters

Please read the security information below before using this.

General behaviour

GitOpsSets can be suspended, by setting the spec.suspend flag to be true.

When this is the case, updates will not be applied, no resources created or deleted.

In addition, a manual reconciliation can be requested by annotating a GitOpsSet with the reconcile.fluxcd.io/requestedAt annotation.

Generation

The simplest generator is the List generator.

apiVersion: templates.weave.works/v1alpha1
kind: GitOpsSet
metadata:
  name: gitopsset-sample
spec:
  generators:
    - list:
        elements:
          - env: dev
            team: dev-team
          - env: production
            team: ops-team
          - env: staging
            team: ops-team

The elements in there are a set JSON of objects1, there are three in this example, and each of them has two keys, env and team.

Other generators provide different sets of keys and values.

The generators documentation below provides more information on what the other generators output.

Rendering templates

Templates are Kubernetes resources in YAML format.

Each template is rendered for each element generated by the generators.

apiVersion: templates.weave.works/v1alpha1
kind: GitOpsSet
metadata:
  name: gitopsset-sample
spec:
  generators:
    - list:
        elements:
          - env: dev
            team: dev-team
          - env: production
            team: ops-team
          - env: staging
            team: ops-team
  templates:
    - content:
        kind: Kustomization
        apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
        metadata:
          name: "{{ .Element.env }}-demo"
          labels:
            app.kubernetes.io/name: go-demo
            app.kubernetes.io/instance: "{{ .Element.env }}"
            com.example/team: "{{ .Element.team }}"
        spec:
          interval: 5m
          path: "./examples/kustomize/environments/{{ .Element.env }}"
          prune: true
          sourceRef:
            kind: GitRepository
            name: go-demo-repo

The generated elements are provided to the template in the Element scope, so .Element.dev refers to the dev field from the List element.

The output from all generators is exposed in the Element scope, not just List generators.

In addition to the .Element field, a .ElementIndex is also available, this represents the zero-based index into the set of generated elements.

NOTE: It's not recommended that you use this to name resources where the ordering of the queries for generating the elements is not guaranteed to be ordered, otherwise you could generate churn in resources as we look for resources by name when updating them, so, .ElementIndex 1 may not be the same as .ElementIndex 1 was the previous time, and this could cause resources to be updated unnecessarily with undesirable effects.

Repeating templates

The output from a generator is an array of JSON objects1, the keys of which can contain repeating elements, either further JSON objects, or scalar values.

It can be desirable to repeat a template for a repeated element in a generated value.

apiVersion: templates.weave.works/v1alpha1
kind: GitOpsSet
metadata:
  name: repeated-gitopsset-sample
spec:
  generators:
    - list:
        elements:
          - env: dev
            team: dev-team
            teams:
              - name: "team1"
              - name: "team2"
              - name: "team3"
          - env: staging
            team: staging-team
            teams:
              - name: "team4"
              - name: "team5"
              - name: "team6"
  templates:
    - repeat: "{ .teams }"
      content:
        kind: ConfigMap
        apiVersion: v1
        metadata:
          name: "{{ .Repeat.name }}-demo"
        data:
          name: "{{ .Repeat.name }}-demo"
          team: "{{ .Element.team }}"

The template repeat field is a JSONPath expression that is applied to each element during the template rendering.

Templates that use repeat will have two separate scopes for the template params, .Element which is the top-level element generated by the generator, and the additional .Repeat scope, which is the repeating element.

In this case, six different ConfigMaps are generated, three for the "dev-team" and three for the "staging-team".

As with the .ElementIndex, for repeated elements both .ElementIndex and .RepeatIndex are available.

Delimiters

The default delimiters for the template engine are {{ and }}, which is the same as the Go template engine.

These can be changed by adding an annotation to the GitOpsSet:

apiVersion: templates.weave.works/v1alpha1
kind: GitOpsSet
metadata:
  name: gitopsset-sample
  annotations:
    templates.weave.works/delimiters: "${{,}}"

Changing the delimiters can useful for:

  • Nesting GitopsSets within each other, as the default delimiters will conflict
  • Providing unquoted values to yaml

Unquoted values

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 the ${{,}} 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.

Unquoted values allow you to include objects in your templates too.

apiVersion: templates.weave.works/v1alpha1
kind: GitOpsSet
metadata:
  name: gitopsset-sample
  annotations:
    templates.weave.works/delimiters: "${{,}}"
spec:
  generators:
    - list:
        elements:
          - env: dev
            resources:
              limits:
                cpu: 100m
                memory: 128Mi
          - env: staging
            resources:
              limits:
                cpu: 100m
                memory: 128Mi
  templates:
    - content:
        kind: Deployment
        apiVersion: apps/v1
        metadata:
          name: go-demo
        spec:
          template:
            spec:
              containers:
                - name: go-demo
                  image: weaveworks/go-demo:0.2.0
                  resources: ${{ .Element.resources | toJson }}

With the default {{,}} delimiters this would fail as the "resources" field would need to be quoted.

Again, if we quote them we would get a string value, not an object.

Generators

We currently provide these generators:

List generator

This is the simplest generator, which is a hard-coded array of JSON objects, described as YAML mappings.

GitRepository generator

The GitRepository generator operates on Flux GitRepositories.

When a GitRepository is updated, this will trigger a regeneration of templates.

The generator operates in two different ways, you can parse files (YAML or JSON) into Elements, or you can scan directories for subdirectories.

Generation from files

apiVersion: templates.weave.works/v1alpha1
kind: GitOpsSet
metadata:
  name: repository-sample
spec:
  generators:
    - gitRepository:
        repositoryRef: go-demo-repo
        files:
          - path: examples/generation/dev.yaml
          - path: examples/generation/production.yaml
          - path: examples/generation/staging.yaml
  templates:
    - content:
        kind: Kustomization
        apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
        metadata:
          name: "{{ .Element.env }}-demo"
          labels:
            app.kubernetes.io/name: go-demo
            app.kubernetes.io/instance: "{{ .Element.env }}"
            com.example/team: "{{ .Element.team }}"
        spec:
          interval: 5m
          path: "./examples/kustomize/environments/{{ .Element.env }}"
          prune: true
          sourceRef:
            kind: GitRepository
            name: go-demo-repo

In this example, a Flux GitRepository called go-demo-repo in the same namespace as the GitOpsSet will be tracked, and Kustomization resources will be generated from the three files listed.

These files can be JSON or YAML.

In this example we expect to find the following structure in the files:

env: dev
team: developers

Changes pushed to the GitRepository will result in rereconciliation of the templates into the cluster.

For security reasons, you need to explicitly list out the files that the generator should parse.

Generation from directories

apiVersion: templates.weave.works/v1alpha1
kind: GitOpsSet
metadata:
  labels:
    app.kubernetes.io/name: gitopsset
    app.kubernetes.io/instance: gitopsset-sample
    app.kubernetes.io/part-of: gitopssets-controller
    app.kubernetes.io/managed-by: kustomize
    app.kubernetes.io/created-by: gitopssets-controller
  name: repository-sample
spec:
  generators:
    - gitRepository:
        repositoryRef: go-demo-repo
        directories:
          - path: examples/kustomize/environments/*
  templates:
    - content:
        kind: Kustomization
        apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
        metadata:
          name: "{{ .Element.Base }}-demo"
          labels:
            app.kubernetes.io/name: go-demo
            app.kubernetes.io/instance: "{{ .Element.Base }}"
            com.example/team: "{{ .Element.Base }}"
        spec:
          interval: 5m
          path: "{{ .Element.Directory }}"
          prune: true
          sourceRef:
            kind: GitRepository
            name: go-demo-repo

In this example, a Flux GitRepository called go-demo-repo in the same namespace as the GitOpsSet will be tracked, and Kustomization resources are generated from paths within the examples/kustomize/environments/* directory within the repository.

Each generated element has two keys, .Element.Directory which will be a repo-relative path and .Element.Base which contains the last element of the path, for example, for a directory ./examples/kustomize/environments/production this will be production.

It is also possible to exclude paths from the generated list, for example, if you do not want to generate for a directory you can exclude it with:

apiVersion: templates.weave.works/v1alpha1
kind: GitOpsSet
metadata:
  name: repository-sample
spec:
  generators:
    - gitRepository:
        repositoryRef: go-demo-repo
        directories:
          - path: examples/kustomize/environments/*
          - path: examples/kustomize/environments/production
            exclude: true
  templates:
    - content:

In this case, all directories that are subdirectories of examples/kustomize/environments will be generated, but not examples/kustomize/environments/production.

Note: The directory tree detection is restricted to the same directory as the path, no recursion is done.

In fact the path is treated as a Glob.

OCIRepository generator

The OCIRepository generator operates on Flux OCIRepositories.

When an OCIRepository is updated, this will trigger a regeneration of templates.

The OCIRepository generator operates in exactly the same way as the GitRepository generator, except it operates on OCIRepositories.

Generation from files

apiVersion: templates.weave.works/v1alpha1
kind: GitOpsSet
metadata:
  name: oci-repository-sample
spec:
  generators:
    - ociRepository:
        repositoryRef: go-demo-oci-repo
        files:
          - path: examples/generation/dev.yaml
          - path: examples/generation/production.yaml
          - path: examples/generation/staging.yaml
  templates:
    - content:
        kind: Kustomization
        apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
        metadata:
          name: "{{ .Element.env }}-demo"
          labels:
            app.kubernetes.io/name: go-demo
            app.kubernetes.io/instance: "{{ .Element.env }}"
            com.example/team: "{{ .Element.team }}"
        spec:
          interval: 5m
          path: "./examples/kustomize/environments/{{ .Element.env }}"
          prune: true
          sourceRef:
            kind: GitRepository
            name: go-demo-repo

PullRequests generator

This will require to make authenticated requests to your Git hosting provider e.g. GitHub, GitLab, Bitbucket etc.

It does only require read-only access, but all API tokens should be guarded as carefully as possible, what is a "read-only" token today, might become a token with higher-privilege in the future.

There have been many security compromises using API access tokens, do not let this happen to you!

apiVersion: templates.weave.works/v1alpha1
kind: GitOpsSet
metadata:
  name: pull-requests-sample
spec:
  generators:
    - pullRequests:
        interval: 5m
        driver: github
        repo: bigkevmcd/go-demo
        secretRef:
          name: github-secret
  templates:
    - content:
        apiVersion: source.toolkit.fluxcd.io/v1beta2
        kind: GitRepository
        metadata:
          name: "pr-{{ .Element.Number }}-gitrepository"
          namespace: default
        spec:
          interval: 5m0s
          url: "{{ .Element.CloneURL }}"
          ref:
            branch: "{{ .Element.Branch }}"
    - content:
        apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
        kind: Kustomization
        metadata:
          name: "pr-{{ .Element.Number }}-demo"
          namespace: default
        spec:
          interval: 5m
          path: "./examples/kustomize/environments/dev"
          prune: true
          targetNamespace: "{{ .Element.Branch }}-ns"
          sourceRef:
            kind: GitRepository
            name: "pr-{{ .Element.Number }}-gitrepository"

This example will poll "github.com/bigkevmcd/go-demo" for open pull requests and trigger the deployment of these by creating a Flux GitRepository and a Kustomization to deploy.

As the generator only queries open pull requests, when a PR is closed, the generated resources will be removed.

For non-public installations, you can configure the serverURL field and point it to your own installation.

The driver field can be github or gitlab or bitbucketserver, other options can be supported from go-scm.

The forks flag field can be used to indicate whether to include forks in the target pull requests or not. If set to true any pull request from a fork repository will be included, otherwise if false or not indicated the pull requests from fork repositories are discarded.

Additionally labels can be provided for querying pull requests with matching labels e.g.

- pullRequests:
    interval: 5m
    driver: github
    repo: bigkevmcd/go-demo
    secretRef:
      name: github-secret
    forks: false
    labels:
      - deploy

The fields emitted by the pull-request are as follows:

  • Number this is generated as a string representation
  • Branch this is the source branch
  • HeadSHA this is the SHA of the commit in the merge branch
  • CloneURL this is the HTTPS clone URL for this repository
  • CloneSSHURL this is the SSH clone URL for this repository
  • Fork this indicates whether the pull request is from a fork (true) or not (false)

Create a read-only token that can list Pull Requests, and store it in a secret:

$ kubectl create secret generic github-secret \
  --from-literal password=<insert access token here>

Matrix generator

The matrix generator doesn't generate resources by itself. It combines the results of generation from other generators e.g.:

apiVersion: templates.weave.works/v1alpha1
kind: GitOpsSet
metadata:
  name: matrix-sample
spec:
  generators:
    - matrix:
        generators:
          - gitRepository:
              repositoryRef: go-demo-repo
              files:
                - path: examples/generation/dev.yaml
                - path: examples/generation/production.yaml
                - path: examples/generation/staging.yaml
          - list:
              elements:
                - cluster: dev-cluster
                  version: 1.0.0

Given the files mentioned all have the following structure:

env: dev
team: developers

This will result in three sets of generated parameters, which are a combination of the maps in the files in the gitRepository, and the elements in the list generator, this can result in a combinatorial explosion of resources being created in your cluster.

- env: dev
  team: developers
  cluster: dev-cluster
  version: 1.0.0
- env: staging
  team: staging-team
  cluster: dev-cluster
  version: 1.0.0
- env: production
  team: production-team
  cluster: dev-cluster
  version: 1.0.0

These can be referenced in the templates, note that all keys in the merged generators from the Matrix are contained in the Element scope.

apiVersion: templates.weave.works/v1alpha1
kind: GitOpsSet
metadata:
  name: matrix-sample
spec:
  generators:
    - matrix:
        generators:
          - gitRepository:
              repositoryRef: go-demo-repo
              files:
                - path: examples/generation/dev.yaml
                - path: examples/generation/production.yaml
                - path: examples/generation/staging.yaml
          - list:
              elements:
                - cluster: dev-cluster
                  version: 1.0.0
  templates:
    - content:
        kind: Kustomization
        apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
        metadata:
          name: "{{ .Element.env }}-demo"
          labels:
            app.kubernetes.io/name: go-demo
            app.kubernetes.io/instance: "{{ .Element.env }}"
            com.example/team: "{{ .Element.team }}"
            com.example/cluster: "{{ .Element.cluster }}"
            com.example/version: "{{ .Element.version }}"
        spec:
          interval: 5m
          path: "./examples/kustomize/environments/{{ .Element.env }}"
          prune: true
          sourceRef:
            kind: GitRepository
            name: go-demo-repo

Optional Name for Matrix elements

If you want to use two generators in a Matrix that output the same fields, they will collide, for example, the ImagePolicy generator outputs a latestImage field, if you have two, they will collide.

You can provide a name for the generator in the Matrix:

apiVersion: templates.weave.works/v1alpha1
kind: GitOpsSet
metadata:
  name: matrix-sample
spec:
  generators:
    - matrix:
        generators:
          - name: gen1
            gitRepository:
              repositoryRef: go-demo-repo
              files:
                - path: examples/generation/dev.yaml
                - path: examples/generation/production.yaml
                - path: examples/generation/staging.yaml
          - name: gen2
            list:
              elements:
                - cluster: dev-cluster
                  version: 1.0.0
  templates:
    - content:
        kind: Kustomization
        apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
        metadata:
          name: "{{ .Element.gen1.env }}-demo"
          labels:
            app.kubernetes.io/name: go-demo
            app.kubernetes.io/instance: "{{ .Element.gen1.env }}"
            com.example/team: "{{ .Element.gen1.team }}"
            com.example/cluster: "{{ .Element.gen2.cluster }}"
            com.example/version: "{{ .Element.gen2.version }}"
        spec:
          interval: 5m
          path: "./examples/kustomize/environments/{{ .Element.gen1.env }}"
          prune: true
          sourceRef:
            kind: GitRepository
            name: go-demo-repo

The name value is used as a key in the generated results.

The example above will yield:

- gen1:
    env: dev
    team: developers
  gen2:
    cluster: dev-cluster
    ersion: 1.0.0
- gen1:
    env: staging
    team: staging-team
  gen2:
    cluster: dev-cluster
    version: 1.0.0
- gen1:
    env: production
    team: production-team
  gen2:
    cluster: dev-cluster
    version: 1.0.0

Single Element for Matrix

A matrix generator will normally generate a cartesian result, but you can also generate a single result.

apiVersion: templates.weave.works/v1alpha1
kind: GitOpsSet
metadata:
  name: single-element-matrix-sample
spec:
  generators:
    - matrix:
        singleElement: true
        generators:
        - name: staging
          cluster:
            selector:
              matchLabels:
                env: staging
        - name: production
          cluster:
            selector:
              matchLabels:
                env: production

This would query for clusters matching the respective labels.

The resulting output would look like this (in YAML):

- production:
  - ClusterAnnotations: {}
    ClusterLabels:
      env: production
    ClusterName: production-cluster1
    ClusterNamespace: clusters
  - ClusterAnnotations: {}
    ClusterLabels:
      env: production
    ClusterName: production-cluster2
    ClusterNamespace: clusters
  staging:
  - ClusterAnnotations: {}
    ClusterLabels:
      env: staging
    ClusterName: staging-cluster1
    ClusterNamespace: clusters
  - ClusterAnnotations: {}
    ClusterLabels:
      env: staging
    ClusterName: staging-cluster2
    ClusterNamespace: clusters

Compare this with the alternative without the singleElement flag:

- production:
    ClusterAnnotations: {}
    ClusterLabels:
      env: production
    ClusterName: production-cluster1
    ClusterNamespace: clusters
  staging:
    ClusterAnnotations: {}
    ClusterLabels:
      env: staging
    ClusterName: staging-cluster1
    ClusterNamespace: clusters
- production:
    ClusterAnnotations: {}
    ClusterLabels:
      env: production
    ClusterName: production-cluster2
    ClusterNamespace: clusters
  staging:
    ClusterAnnotations: {}
    ClusterLabels:
      env: staging
    ClusterName: staging-cluster1
    ClusterNamespace: clusters
- production:
    ClusterAnnotations: {}
    ClusterLabels:
      env: production
    ClusterName: production-cluster1
    ClusterNamespace: clusters
  staging:
    ClusterAnnotations: {}
    ClusterLabels:
      env: staging
    ClusterName: staging-cluster2
    ClusterNamespace: clusters
- production:
    ClusterAnnotations: {}
    ClusterLabels:
      env: production
    ClusterName: production-cluster2
    ClusterNamespace: clusters
  staging:
    ClusterAnnotations: # omitted
    ClusterLabels:
      env: staging
    ClusterName: staging-cluster2
    ClusterNamespace: clusters

In the singleElement case, there is only one generated element, only one template will be rendered for each content item.

If the Matrix generators are unnamed, they will be grouped under a top-level .Matrix name.

apiClient generator

This generator is configured to poll an HTTP endpoint and parse the result as the generated values.

This will poll an endpoint on the interval, instead of using the simpler to use PullRequest generator, you can access GitHub's API with the APIClient generator.

The PullRequest generator is simpler to use, and works across multiple different git-providers.

The GitHub documentation for the API endpoint shows:

curl \
  -H "Accept: application/vnd.github+json" \
  -H "Authorization: Bearer <YOUR-TOKEN>"\
  -H "X-GitHub-Api-Version: 2022-11-28" \
  https://api.github.com/repos/OWNER/REPO/pulls

This can be translated into...

apiVersion: templates.weave.works/v1alpha1
kind: GitOpsSet
metadata:
  labels:
    app.kubernetes.io/name: gitopsset
    app.kubernetes.io/instance: gitopsset-sample
    app.kubernetes.io/part-of: gitopssets-controller
    app.kubernetes.io/managed-by: kustomize
    app.kubernetes.io/created-by: gitopssets-controller
  name: api-client-sample
spec:
  generators:
    - apiClient:
        interval: 5m
        endpoint: https://api.github.com/repos/bigkevmcd/go-demo/pulls
        headersRef:
          name: github-secret
          kind: Secret
  templates:
    - content:
        apiVersion: source.toolkit.fluxcd.io/v1beta2
        kind: GitRepository
        metadata:
          name: "pr-{{ .Element.id | toJson}}-gitrepository"
          namespace: default
        spec:
          interval: 5m0s
          url: "{{ .Element.head.repo.clone_url }}"
          ref:
            branch: "{{ .Element.head.ref }}"
    - content:
        apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
        kind: Kustomization
        metadata:
          name: "pr-{{ .Element.id | toJson }}-demo"
          namespace: default
        spec:
          interval: 5m
          path: "./examples/kustomize/environments/dev"
          prune: true
          targetNamespace: "{{ .Element.head.ref }}-ns"
          sourceRef:
            kind: GitRepository
            name: "pr-{{ .Element.id | toJson }}-gitrepository"

As with the Pull Request generator, this also requires a secret token to be able to access the API

We need to pass this as an HTTP header.

apiVersion: v1
kind: Secret
metadata:
  name: github-secret
  namespace: default
type: Opaque
stringData:
  Accept: application/vnd.github+json
  Authorization: Bearer ghp_<redacted>
  X-GitHub-Api-Version: "2022-11-28"

The keys in the secret match the command-line example using curl.

Unlike the Pull Request generator, you need to figure out the paths to the elements yourself.

APIClient JSONPath

Not all APIs return an array of JSON objects, sometimes it's nested within a result type structure e.g.

{
  "things": [
    {
      "env": "dev",
      "team": "dev-team"
    },
    {
      "env": "production",
      "team": "opts-team"
    },
    {
      "env": "staging",
      "team": "opts-team"
    }
  ]
}

You can use JSONPath to extract the fields from this data...

apiVersion: templates.weave.works/v1alpha1
kind: GitOpsSet
metadata:
  labels:
    app.kubernetes.io/name: gitopsset
    app.kubernetes.io/instance: gitopsset-sample
    app.kubernetes.io/part-of: gitopssets-controller
    app.kubernetes.io/managed-by: kustomize
    app.kubernetes.io/created-by: gitopssets-controller
  name: api-client-sample
spec:
  generators:
    - apiClient:
        interval: 5m
        endpoint: https://api.example.com/demo
        jsonPath: "{ $.things }"

This will generate three maps for templates, with just the env and team keys.

APIClient POST body

Another piece of functionality in the APIClient generator is the ability to POST JSON to the API.

apiVersion: templates.weave.works/v1alpha1
kind: GitOpsSet
metadata:
  labels:
    app.kubernetes.io/name: gitopsset
    app.kubernetes.io/instance: gitopsset-sample
    app.kubernetes.io/part-of: gitopssets-controller
    app.kubernetes.io/managed-by: kustomize
    app.kubernetes.io/created-by: gitopssets-controller
  name: api-client-sample
spec:
  generators:
    - apiClient:
        interval: 5m
        endpoint: https://api.example.com/demo
        body:
          name: "testing"
          value: "testing2"

This will send a request body as JSON (Content-Type "application/json") to the server and interpret the result.

The JSON body sent will look like this:

{ "name": "testing", "value": "testing2" }

APIClient simple results

Instead of using the JSONPath to extract from a complex structure, you can configure the result to be a single element.

apiVersion: templates.weave.works/v1alpha1
kind: GitOpsSet
metadata:
  labels:
    app.kubernetes.io/name: gitopsset
    app.kubernetes.io/instance: gitopsset-sample
    app.kubernetes.io/part-of: gitopssets-controller
    app.kubernetes.io/created-by: gitopssets-controller
  name: api-client-sample
spec:
  generators:
    - apiClient:
        singleElement: true
        interval: 5m
        endpoint: https://api.example.com/demo

Whatever result is parsed from the API endpoint will be returned as a map in a single element.

For generation, you might need to use the repeat mechanism to generate repeating results.

APIClient Custom CA

If the API endpoint you are accessing requires a custom CA you can provide this via the secret field.

apiVersion: templates.weave.works/v1alpha1
kind: GitOpsSet
metadata:
  labels:
    app.kubernetes.io/name: gitopsset
    app.kubernetes.io/instance: gitopsset-sample
    app.kubernetes.io/part-of: gitopssets-controller
    app.kubernetes.io/created-by: gitopssets-controller
  name: api-client-sample
spec:
  generators:
    - apiClient:
        singleElement: true
        interval: 5m
        endpoint: https://api.example.com/demo
        secretRef:
          name: https-ca-credentials

This secret should look like this:

---
apiVersion: v1
kind: Secret
metadata:
  name: https-ca-credentials
type: Opaque
data:
  caFile: <BASE64>

The request will be made with the custom CA.

Cluster generator

The cluster generator generates from in-cluster GitOpsCluster resources.

For example, this GitOpsSet will generate a Kustomization resource for each cluster matching the Label selector.

apiVersion: templates.weave.works/v1alpha1
kind: GitOpsSet
metadata:
  name: cluster-sample
spec:
  generators:
    - cluster:
        selector:
          matchLabels:
            env: dev
            team: dev-team
  templates:
    - content:
        kind: Kustomization
        apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
        metadata:
          name: "{{ .Element.ClusterName }}-demo"
          labels:
            app.kubernetes.io/name: go-demo
            app.kubernetes.io/instance: "{{ .Element.ClusterName }}"
            com.example/team: "{{ .Element.ClusterLabels.team }}"
        spec:
          interval: 5m
          path: "./examples/kustomize/environments/{{ .Element.ClusterLabels.env }}"
          prune: true
          sourceRef:
            kind: GitRepository
            name: go-demo-repo

The following fields are generated for each GitOpsCluster.

  • ClusterName the name of the cluster
  • ClusterNamespace the namespace that this cluster is from
  • ClusterLabels the labels from the metadata field on the GitOpsCluster
  • ClusterAnnotations the annotations from the metadata field on the GitOpsCluster

If the selector is not provided, all clusters from all namespaces will be returned:

apiVersion: templates.weave.works/v1alpha1
kind: GitOpsSet
metadata:
  name: cluster-sample
spec:
  generators:
    - cluster: {}

Otherwise if the selector is empty, no clusters will be generated:

apiVersion: templates.weave.works/v1alpha1
kind: GitOpsSet
metadata:
  name: cluster-sample
spec:
  generators:
    - cluster:
        selector: {}

ImagePolicy generator

The ImagePolicy generator works with the Flux Image Automation.

When an ImagePolicy is updated, this will trigger a regeneration of templates.

The generated elements have the following fields:

  • latestImage - the latest image from the status field on the ImagePolicy
  • latestTag - only the tag part of the latestImage, e.g. will be v0.1 for an image of "testing/image:v0.1"
  • previousImage - the previous image from the status field on the ImagePolicy

This can be used simply, to create a deployment with an image...or, combined with a Matrix generator, to manage multiple workloads with the same image.

apiVersion: templates.weave.works/v1alpha1
kind: GitOpsSet
metadata:
  name: imagepolicy-example
  namespace: default
spec:
  generators:
    - imagePolicy:
        policyRef: podinfo
  templates:
    - content:
        kind: ConfigMap
        apiVersion: v1
        metadata:
          name: "demo-configmap"
        data:
          image: "{{ .Element.latestImage }}"

In this example, a ConfigMap is generated containing the latest image whenever an ImagePolicy called podinfo is updated.

Combined in a Matrix, like this, it will generate two ConfigMaps with the values.

apiVersion: templates.weave.works/v1alpha1
kind: GitOpsSet
metadata:
  name: imagepolicy-matrix-example
  namespace: default
spec:
  generators:
    - matrix:
        generators:
          - imagePolicy:
             policyRef: podinfo
          - list:
              elements:
                - cluster: dev-cluster
                  version: 1.0.0
                - cluster: prod-cluster
                  version: 1.0.0
  templates:
    - content:
        kind: ConfigMap
        apiVersion: v1
        metadata:
          name: "demo-configmap-{{ .Element.cluster }}"
        data:
          image: "{{ .Element.latestImage }}"
          cluster: "{{ .Element.cluster }}"
          version: "{{ .Element.version }}"

The resulting ConfigMaps look like this:

$ kubectl get configmaps
NAME                          DATA   AGE
demo-configmap-dev-cluster    3      3m19s
demo-configmap-prod-cluster   3      3m19s

With the templated fields like this:

apiVersion: v1
kind: ConfigMap
metadata:
  name: demo-configmap-dev-cluster
  namespace: default
data:
  cluster: dev-cluster
  image: stefanprodan/podinfo:5.1.4
  version: 1.0.0
apiVersion: v1
kind: ConfigMap
metadata:
  name: demo-configmap-prod-cluster
  namespace: default
data:
  cluster: prod-cluster
  image: stefanprodan/podinfo:5.1.4
  version: 1.0.0

Config generator

The Config generator with Kubernetes ConfigMaps and Secrets.

When an ConfigMap or Secret is updated, this will trigger a regeneration of templates.

This can be used simply, to create a resource with an config variable...or, combined with a Matrix generator, to manage multiple workloads with the same values.

With the existing ConfigMap

apiVersion: v1
kind: ConfigMap
metadata:
  name: test-cm
data:
  name: test-config
  demo: test-value

And the GitOpsSet below

apiVersion: templates.weave.works/v1alpha1
kind: GitOpsSet
metadata:
  name: config-sample
spec:
  generators:
    - config:
        kind: ConfigMap
        name: test-cm
  templates:
    - content:
        kind: ConfigMap
        apiVersion: v1
        metadata:
          name: "{{ .Element.name }}-demo"
          labels:
            app.kubernetes.io/name: go-demo
            app.kubernetes.io/instance: "{{ .Element.name }}"
        data:
          generatedValue: "{{ .Element.demo }}"

In this example, a new ConfigMap is generated containing the value of the "demo" field from the existing ConfigMap test-cm.

As with the other generators, the Config generator can be combined with other generators:

This will generate two ConfigMaps with the values.

apiVersion: templates.weave.works/v1alpha1
kind: GitOpsSet
metadata:
  name: imagepolicy-matrix-example
  namespace: default
spec:
  generators:
    - matrix:
        generators:
          - config:
              kind: ConfigMap
              name: test-cm
          - list:
              elements:
                - cluster: dev-cluster
                  version: 1.0.0
                - cluster: prod-cluster
                  version: 1.0.0
  templates:
    - content:
        kind: ConfigMap
        apiVersion: v1
        metadata:
          name: "demo-configmap-{{ .Element.cluster }}"
        data:
          generatedValue: "{{ .Element.demo }}"
          cluster: "{{ .Element.cluster }}"
          version: "{{ .Element.version }}"

The resulting ConfigMaps look like this:

$ kubectl get configmaps
NAME                          DATA   AGE
demo-configmap-dev-cluster    3      3m19s
demo-configmap-prod-cluster   3      3m19s

With the templated fields like this:

apiVersion: v1
kind: ConfigMap
metadata:
  name: demo-configmap-dev-cluster
  namespace: default
data:
  cluster: dev-cluster
  generatedValue: test-value
  version: 1.0.0
apiVersion: v1
kind: ConfigMap
metadata:
  name: demo-configmap-prod-cluster
  namespace: default
data:
  cluster: prod-cluster
  generatedValue: test-value
  version: 1.0.0

Templating functions

Currently, the Sprig functions are available in the templating, with some functions removed2 for security reasons.

In addition, we also provide two additional functions:

  • sanitize - sanitises strings to be compatible with Kubernetes DNS name requirements
  • getordefault - gets a key from the .Element or defaults to another value.

The examples below assume an element that looks like this:

{
  "team": "engineering dev"
}

sanitize template function

And a template that looks like this:

kind: Service
metadata:
  name: {{ sanitize .Element.team }}-demo

This would output:

kind: Service
metadata:
  name: engineeringdev-demo

getordefault

For template that looks like this:

kind: Service
metadata:
  name: {{ getordefault .Element "name" "defaulted" }}-demo

This would output:

kind: Service
metadata:
  name: defaulted-demo

If the key to get does exist in the .Element it will be inserted, the "default" is only inserted if it doesn't exist.

Security

WARNING generating resources and applying them directly into your cluster can be dangerous to the health of your cluster.

This is especially true for the GitRepository generator, where it may not be obvious to the author of the files, or the author of the template the consequences of the template rendering.

The default ServiceAccount that is used by the gitopssets-controller is extremely limited, and can not create resources, you will need to explicitly grant permissions to create any of the resources you declare in the template, missing permissions will appear in the controller logs.

It is not recommended that you create a role with blanket permissions, under the right circumstances, someone could accidentally or maliciously overwrite the cluster control-plane, which could be very dangerous.

Limiting via service-accounts

You can configure the service-account that is used to create resources.

apiVersion: templates.weave.works/v1alpha1
kind: GitOpsSet
metadata:
  name: matrix-sample
spec:
  # the controller will impersonate this service account
  serviceAccountName: test-sa
  generators:
    - list:
        elements:
          - env: dev
            team: dev-team
          - env: production
            team: ops-team
          - env: staging
            team: ops-team
  templates:
    - content:
        kind: Kustomization
        apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
        metadata:
          name: "{{ .Element.env }}-demo"
          labels:
            app.kubernetes.io/name: go-demo
            app.kubernetes.io/instance: "{{ .Element.env }}"
            com.example/team: "{{ .Element.team }}"
        spec:
          interval: 5m
          path: "./examples/kustomize/environments/{{ .Element.env }}"
          prune: true
          sourceRef:
            kind: GitRepository
            name: go-demo-repo

gitopsset-controller configuration

The enabled generators can be configured via the --enabled-generators flag, which takes a comma separated list of generators to enable.

The default is to enable all generators.

For example to enable only the List and GitRepository generators:

--enabled-generators=List,GitRepository

When a GitOpsSet that uses disabled generators is created, the disabled generators will be silently ignored.

Kubernetes Process Limits

GitOpsSets can be memory-hungry, for example, the Matrix generator will generate a cartesian result with multiple copies of data.

The OCI and GitRepository generators will extract tarballs, the API Generator queries upstream APIs and parses the JSON, and the Config generators will load Secret and ConfigMap resources, all these can lead to using significant amounts of memory.

Extracting tarballs can also prove to be CPU intensive, especially where there are lots of files, and you have a very frequent regeneration period.

To this end, you will need to monitor the controller metrics, and maybe increase the limits available to the controller.

For example, to increase the amount of memory available to the controller:

resources:
  limits:
    cpu: 1000m
    memory: 2Gi
  requests:
    cpu: 100m
    memory: 64Mi

Notifications

Events are enabled which will trigger Kubernetes events when successful reconciliation occurs with a Normal event or when reconciliation fails with an Error event. Fluxcd's Events package is used including the EventRecorder to record these events.

To configure receiving the recorded events on a specific host, this can be provided via the --events-addr flag in RUN_ARGS when starting the controller. This can be any HTTP endpoint.

See fluxcd event for the struct of the event created.

Footnotes

  1. These are written as YAML mappings 2

  2. The following functions are removed "env", "expandenv", "getHostByName", "genPrivateKey", "derivePassword", "sha256sum", "base", "dir", "ext", "clean", "isAbs", "osBase", "osDir", "osExt", "osClean", "osIsAbs"