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,/diffand 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, reasonDeployFailed. An unknowntypeorfrom.kindgives reasonUnsupported. The failure stage isRendering.
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
"". namespacecompares withmetadata.namespaceof the rendered object. An object withoutmetadata.namespacedoes not match a target with anamespace. convoy adds thedefaultnamespace only when it applies the object.
mutations:
- type: jsonPatch
target:
group: apps
version: v1
kind: Deployment
name: podinfo
patches:
- op: replace
path: /spec/replicas
value: 3Mutation types#
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:
- Executes the template with the context of the object.
- Parses the output as YAML.
- 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:
{"metadata": {"labels": {"region": "region" in cluster ? cluster.region : "none"}}}syncWave#
waveStrategy sets the sync-wave annotations on each matching object:
| Field | Annotation |
|---|---|
wave | convoy.kumodo.io/sync-wave, always |
strategy | convoy.kumodo.io/sync-strategy, if set |
timeout | convoy.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:
| Name | Go template | CEL | Value |
|---|---|---|---|
cluster | {{ .cluster.region }}, {{ .cluster.name }} | cluster.region, cluster.name | The spec.metadata keys of the target Cluster, and its name |
self | {{ .self.name }} | self.name | The name, namespace, labels and annotations of the Release |
object | {{ .object.metadata.name }} | object.metadata.name | The 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
clusterkey gives an empty string. - A missing key in
selforobjectgives the text<no value>. - A path through a missing map is an error. For example,
{{ .object.spec.foo.bar }}fails whenfoodoes not exist.
Content from a source#
from reads the content of a goTemplate, strategicMergePatch or celTransform mutation from
another object:
from.kind | from.path | Notes |
|---|---|---|
GitRepository | The path of a file in the repository | from.ref selects the branch, tag or commit. convoy fetches it like a source. |
ConfigMap | A key in data | |
Secret | A key in data |
- convoy uses
fromafter theinlinedocuments. A mutation can have both. - convoy executes
from.nameandfrom.pathas Go templates withclusterandself, one time for each mutation.objectis not available there. from.namespaceis 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.
fromhas nooptionalfield. - A change to the referenced object does not start a reconcile. convoy reads it again at the next reconcile.
# 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.
kubectl convoy release mutations get podinfoAdmission checks these rules:
typeis one of the five types.jsonPatchhas at least one patch. Each patch has a knownopand apath.goTemplate,strategicMergePatchandcelTransformhaveinlineorfrom.syncWavehaswaveStrategy, with a knownstrategyif set.from.namespaceis 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
internal/render.ApplyMutationsinternal/render/mutate.go:75internal/render.matchesTargetinternal/render/mutate.go:608internal/render.applyGoTemplateinternal/render/mutate.go:276internal/render.applyJSONPatchinternal/render/mutate.go:175internal/render.applyStrategicMergeinternal/render/mutate.go:222internal/render.applyCELTransforminternal/render/mutate.go:399internal/render.applySyncWaveinternal/render/mutate.go:122internal/render.resolveFrominternal/render/mutate.go:538