convoy docs
Search

Mutations

The ordered pipeline that changes rendered objects before convoy applies them.

A mutation changes rendered objects before convoy applies them. mutations is a top-level list of the Release, next to spec. convoy runs the list in order after rendering. Mutations change Kubernetes objects, not Helm values. For Helm values, see Rendering. For the fields and admission rules, see Release mutations.

The pipeline#

  • convoy runs the mutations after the content renderer, and before it calculates the renderRef.
  • Each mutation receives the output of the previous mutation.
  • A mutation changes only the objects that match its target. Other objects pass without a change.
  • The observer runs the same pipeline. /preview, /diff and health use the mutated objects.
  • A mutation cannot add or remove objects. It can only change the objects that it receives.
  • An error in a mutation stops the render. The Release reports Applied=False, reason DeployFailed. An unknown type or from.kind gives reason Unsupported. The failure stage is Rendering.

The render store keeps the mutated objects. A pinned render and the last healthy render come from the store, so convoy does not mutate them again. But convoy runs the mutations at each reconcile before it reads the pin and the rollout. A mutation that fails stops these too.

Mutations do not change lifecycle hook workloads. convoy renders the hook templates in a different step, with the cluster and release context.

For each mutation, status.lastAttempted.resolvedMutations records the index, the type, a contentHash of the mutation content, and the from reference. sourceRevision is never set.

Target#

target selects objects by group, version, kind, name and namespace.

  • An empty field matches all values. A target with all fields empty matches all objects.
  • Each field must be equal to the value in the object. There are no patterns.
  • The group of core kinds is "".
  • namespace compares with metadata.namespace of the rendered object. An object without metadata.namespace does not match a target with a namespace. convoy adds the default namespace only when it applies the object.
yaml
mutations:
  - type: jsonPatch
    target:
      group: apps
      version: v1
      kind: Deployment
      name: podinfo
    patches:
      - op: replace
        path: /spec/replicas
        value: 3

Mutation types#

Mutation types Input: a Deployment, a Service and a ConfigMap web, each with label app=web. Cluster metadata region=eu-west-1, Release web-release. Pick two mutations and their targets, or swap their order. The Go template and the CEL expression are fixed examples: the widget does not run them. strategicMergePatch is not in the widget.
TypeContentEffect on the object
goTemplatemetadata: {labels: {region: "{{ .cluster.region }}"}}Adds the label region: eu-west-1. The label app: web stays.
jsonPatch{op: replace, path: /spec/replicas, value: 3}Changes spec.replicas to 3.
strategicMergePatchspec.template.spec.containers: [{name: web, env: [{name: LOG, value: debug}]}]Adds the variable to the container web. The other fields of the container stay.
celTransform{"metadata": {"annotations": {"owner": self.name}}}Adds the annotation owner with the name of the Release.
syncWavewaveStrategy: {wave: 1, strategy: waitForHealthy}Sets the annotations convoy.kumodo.io/sync-wave: "1" and convoy.kumodo.io/sync-strategy: waitForHealthy.

goTemplate#

Each document is a Go template that gives a partial object. convoy uses the documents of inline first, then the document of from. For each matching object and each document, convoy:

  1. Executes the template with the context of the object.
  2. Parses the output as YAML.
  3. Merges the result into the object as an RFC 7386 merge patch.

The merge patch rules apply. Maps merge key by key. A list replaces the full list. A null value removes the key. The output is an overlay. It does not replace the object.

jsonPatch#

patches is a list of RFC 6902 operations: add, remove, replace, move, copy and test. jsonPatch reads only patches. It does not read inline or from.

convoy serializes the patch list to JSON, and executes it as a Go template for each matching object. A value from a template is always a string.

An operation that fails stops the render. For example, replace on a path that does not exist fails, and a failed test fails. Select the target objects carefully.

strategicMergePatch#

Each document is a Kubernetes strategic merge patch. convoy executes it as a Go template for each matching object, then merges it with the schema of the built-in type. Lists with a merge key merge by that key. For example, containers merge by name.

strategicMergePatch works only for built-in kinds. A custom resource that matches the target stops the render. For custom resources, use jsonPatch, goTemplate or celTransform.

celTransform#

Each document is a CEL expression. The variables are cluster, self and object. The expression must return a map with string keys. convoy merges the map into the object as an RFC 7386 merge patch.

A result that is not a map stops the render. In CEL, a read of a missing map key is an error. To read an optional metadata key, test it first:

text
{"metadata": {"labels": {"region": "region" in cluster ? cluster.region : "none"}}}

syncWave#

waveStrategy sets the sync-wave annotations on each matching object:

FieldAnnotation
waveconvoy.kumodo.io/sync-wave, always
strategyconvoy.kumodo.io/sync-strategy, if set
timeoutconvoy.kumodo.io/sync-timeout, if set

The mutation replaces the value of an annotation that exists. It does not remove an annotation for a field that is not set. syncWave does not read inline or from. See Sync waves.

Template context#

Go templates and CEL expressions read the same values:

NameGo templateCELValue
cluster{{ .cluster.region }}, {{ .cluster.name }}cluster.region, cluster.nameThe spec.metadata keys of the target Cluster, and its name
self{{ .self.name }}self.nameThe name, namespace, labels and annotations of the Release
object{{ .object.metadata.name }}object.metadata.nameThe object, as the previous mutation left it

If the observer cannot find the Cluster, cluster is empty. See Clusters.

Go templates use the option missingkey=zero:

  • A missing cluster key gives an empty string.
  • A missing key in self or object gives the text <no value>.
  • A path through a missing map is an error. For example, {{ .object.spec.foo.bar }} fails when foo does not exist.

Content from a source#

from reads the content of a goTemplate, strategicMergePatch or celTransform mutation from another object:

from.kindfrom.pathNotes
GitRepositoryThe path of a file in the repositoryfrom.ref selects the branch, tag or commit. convoy fetches it like a source.
ConfigMapA key in data
SecretA key in data
  • convoy uses from after the inline documents. A mutation can have both.
  • convoy executes from.name and from.path as Go templates with cluster and self, one time for each mutation. object is not available there.
  • from.namespace is not a template. It must be empty or the namespace of the Release.
  • A missing object, key or file stops the render, and convoy retries. from has no optional field.
  • A change to the referenced object does not start a reconcile. convoy reads it again at the next reconcile.
mutations/configmap-go-template.yaml
# goTemplate content sourced from a ConfigMap.
#
# The template renders a partial overlay that is RFC7386-merged onto each matched
# object (it injects/updates fields, it does not replace the object). Context:
#   .cluster  -> the target Cluster's name (.cluster.name) + its Spec.Metadata
#                keys (e.g. {{ .cluster.region }})
#   .self     -> the Release's name / namespace / labels / annotations
#   .object   -> the object being mutated (e.g. {{ .object.metadata.name }})
#
#   mutations:
#     - type: goTemplate
#       target: { group: apps, version: v1, kind: Deployment }
#       from: { kind: ConfigMap, name: mutation-go-template, path: overlay.yaml }
#   from.name and from.path may themselves be templated with {.cluster,.self}
#   (release-level only, no .object), e.g. name: "{{ .cluster.name }}-overlay".
apiVersion: v1
kind: ConfigMap
metadata:
  name: mutation-go-template
  namespace: default
data:
  overlay.yaml: |
    metadata:
      labels:
        convoy.kumodo.io/release: "{{ .self.name }}"
      annotations:
        convoy.kumodo.io/type: "{{ .cluster.type }}"
        convoy.kumodo.io/role: "{{ .cluster.role }}"

The /mutations subresource#

You can write mutations through the main resource or through /mutations. Kubernetes RBAC controls releases/mutations separately. A write replaces the full list. See Release mutations.

sh
kubectl convoy release mutations get podinfo

Admission checks these rules:

  • type is one of the five types.
  • jsonPatch has at least one patch. Each patch has a known op and a path.
  • goTemplate, strategicMergePatch and celTransform have inline or from.
  • syncWave has waveStrategy, with a known strategy if set.
  • from.namespace is empty or the namespace of the Release.

Admission does not parse templates, compile CEL or check from.kind. These errors show only when convoy renders the Release.

In the code

convoy v0.9.0