The ImageUpdateAutomation
type defines an automation process that will update a git repository,
based on image policy objects in the same namespace.
The updates are governed by marking fields to be updated in each YAML file. For each field marked, the automation process checks the image policy named, and updates the field value if there is a new image selected by the policy. The marker format is shown in the image automation guide.
The v1beta1 version of the API has the same schema and semantics as the v1alpha2 version. To migrate
from v1alpha2, all that is required is to change the apiVersion
field in manifests to the new
version:
apiVersion: image.toolkit.fluxcd.io/v1beta1
Note that v1alpha2 resources will be implicitly and losslessly converted to v1beta1 resources. Until v1alpha2 is deprecated, it is possible to keep using v1alpha2.
To see what has changed between the API version v1alpha1
and this version v1beta1
, read the
section on migration at the bottom. (If you have already migrated from
v1alpha1 to v1alpha2, all you need to do is change the version, as above.)
// ImageUpdateAutomationSpec defines the desired state of ImageUpdateAutomation
type ImageUpdateAutomationSpec struct {
// SourceRef refers to the resource giving access details
// to a git repository.
// +required
SourceRef CrossNamespaceSourceReference `json:"sourceRef"`
// GitSpec contains all the git-specific definitions. This is
// technically optional, but in practice mandatory until there are
// other kinds of source allowed.
// +optional
GitSpec *GitSpec `json:"git,omitempty"`
// Interval gives an lower bound for how often the automation
// run should be attempted.
// +required
Interval metav1.Duration `json:"interval"`
// Update gives the specification for how to update the files in
// the repository. This can be left empty, to use the default
// value.
// +kubebuilder:default={"strategy":"Setters"}
Update *UpdateStrategy `json:"update,omitempty"`
// Suspend tells the controller to not run this automation, until
// it is unset (or set to false). Defaults to false.
// +optional
Suspend bool `json:"suspend,omitempty"`
}
The sourceRef
field refers to the GitRepository
object that has details on how to access the Git
repository to be updated. The kind
field in the reference currently only supports the value
GitRepository
, which is the default.
// CrossNamespaceSourceReference contains enough information to let you locate the
// typed Kubernetes resource object at cluster level.
type CrossNamespaceSourceReference struct {
// API version of the referent.
// +optional
APIVersion string `json:"apiVersion,omitempty"`
// Kind of the referent.
// +kubebuilder:validation:Enum=GitRepository
// +kubebuilder:default=GitRepository
// +required
Kind string `json:"kind"`
// Name of the referent.
// +required
Name string `json:"name"`
// Namespace of the referent, defaults to the namespace of the Kubernetes resource object that contains the reference.
// +optional
Namespace string `json:"namespace,omitempty"`
}
A ImageUpdateAutomation can refer to a GitRepository from a different namespace with
spec.sourceRef.namespace
e.g.:
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageUpdateAutomation
metadata:
name: webapp
namespace: apps
spec:
interval: 5m
sourceRef:
kind: GitRepository # the only valid value, but good practice to be explicit here
name: apps
namespace: flux-system
On multi-tenant clusters, platform admins can disable cross-namespace references with the
--no-cross-namespace-refs=true
flag.
To be able to commit changes back, the referenced GitRepository
object must refer to credentials
with write access; e.g., if using a GitHub deploy key, "Allow write access" should be checked when
creating it. Only the url
, ref
, and secretRef
fields of the GitRepository
are used.
The gitImplementation
field in the referenced GitRepository
is ignored. All
reconciliations are executed using the go-git
implementation.
Other fields particular to how the Git repository is used are in the git
field, described
below.
The update
field is described in its own section below.
The required field interval
gives a period for automation runs, in duration notation;
e.g., "5m"
.
While suspend
has a value of true
, the automation will not run.
The git
field has this definition:
type GitSpec struct {
// Checkout gives the parameters for cloning the git repository,
// ready to make changes. If not present, the `spec.ref` field from the
// referenced `GitRepository` or its default will be used.
// +optional
Checkout *GitCheckoutSpec `json:"checkout,omitempty"`
// Commit specifies how to commit to the git repository.
// +required
Commit CommitSpec `json:"commit"`
// Push specifies how and where to push commits made by the
// automation. If missing, commits are pushed (back) to
// `.spec.checkout.branch` or its default.
// +optional
Push *PushSpec `json:"push,omitempty"`
}
The fields checkout
, commit
and push
are explained in the following sections.
The optional .spec.git.checkout
gives the Git reference to check out. The .ref
value is the same
format as the .ref
field in a GitRepository
.
type GitCheckoutSpec struct {
// Reference gives a branch, tag or commit to clone from the Git
// repository.
// +required
Reference sourcev1.GitRepositoryRef `json:"ref"`
}
When checkout
is given, it overrides the analogous field in the GitRepository
object referenced
in .spec.sourceRef
. You would use this to put automation commits on a different branch than that
you are syncing, for example.
By default the controller will only do shallow clones, but this can be disabled by starting the controller
with --feature-gates=GitShallowClone=false
.
The .spec.git.commit
field gives details to use when making a commit to push to the Git repository:
// CommitSpec specifies how to commit changes to the git repository
type CommitSpec struct {
// Author gives the email and optionally the name to use as the
// author of commits.
// +required
Author CommitUser `json:"author"`
// SigningKey provides the option to sign commits with a GPG key
// +optional
SigningKey *SigningKey `json:"signingKey,omitempty"`
// MessageTemplate provides a template for the commit message,
// into which will be interpolated the details of the change made.
// +optional
MessageTemplate string `json:"messageTemplate,omitempty"`
}
type CommitUser struct {
// Name gives the name to provide when making a commit.
// +optional
Name string `json:"name,omitempty"`
// Email gives the email to provide when making a commit.
// +required
Email string `json:"email"`
}
// SigningKey references a Kubernetes secret that contains a GPG keypair
type SigningKey struct {
// SecretRef holds the name to a secret that contains a 'git.asc' key
// corresponding to the ASCII Armored file containing the GPG signing
// keypair as the value. It must be in the same namespace as the
// ImageUpdateAutomation.
// +required
SecretRef meta.LocalObjectReference `json:"secretRef,omitempty"`
}
The author
field gives the author for commits. For example,
spec:
git:
commit:
author:
name: Fluxbot
email: [email protected]
will result in commits with the author Fluxbot <[email protected]>
.
The optional signingKey
field can be used to provide a key to sign commits with. It holds a
reference to a secret, which is expected to have a file called git.asc
containing an
ASCII-armoured PGP key. If the private key is protected by a password, you can specify the same
in the secret using the passphrase
key.
---
apiVersion: v1
kind: Secret
metadata:
name: signing-key
namespace: default
stringData:
git.asc: |
<ARMOR ENCODED PGP KEY>
passphrase: <private-key-passphrase>
The messageTemplate
field is a string which will be used as a template for the commit message. If
empty, there is a default message; but you will likely want to provide your own, especially if you
want to put tokens in to control how CI reacts to commits made by automation. For example,
spec:
git:
commit:
messageTemplate: |
Automated image update by Flux
[ci skip]
The following section describes what data is available to use in the template.
The message template is a Go text template. The data available to the template have this structure (not reproduced verbatim):
// internal/controller/imageupdateautomation_controller.go
// TemplateData is the type of the value given to the commit message
// template.
type TemplateData struct {
AutomationObject struct {
Name, Namespace string
}
Updated update.Result
}
// pkg/update/result.go
// ImageRef represents the image reference used to replace a field
// value in an update.
type ImageRef interface {
// String returns a string representation of the image ref as it
// is used in the update; e.g., "helloworld:v1.0.1"
String() string
// Identifier returns the tag or digest; e.g., "v1.0.1"
Identifier() string
// Repository returns the repository component of the ImageRef,
// with an implied defaults, e.g., "library/helloworld"
Repository() string
// Registry returns the registry component of the ImageRef, e.g.,
// "index.docker.io"
Registry() string
// Name gives the fully-qualified reference name, e.g.,
// "index.docker.io/library/helloworld:v1.0.1"
Name() string
}
// ObjectIdentifier holds the identifying data for a particular
// object. This won't always have a name (e.g., a kustomization.yaml).
type ObjectIdentifier struct {
Name, Namespace, APIVersion, Kind string
}
// Result reports the outcome of an automated update. It has a nested
// structure file->objects->images. Different projections (e.g., all
// the images, regardless of object) are available via methods.
type Result struct {
Files map[string]FileResult
}
// FileResult gives the updates in a particular file.
type FileResult struct {
Objects map[ObjectIdentifier][]ImageRef
}
These methods are defined on update.Result
:
// Images returns all the images that were involved in at least one
// update.
func (r Result) Images() []ImageRef {
// ...
}
// Objects returns a map of all the objects against the images updated
// within, regardless of which file they appear in.
func (r Result) Objects() map[ObjectIdentifier][]ImageRef {
// ...
}
The methods let you range over the objects and images without descending the data structure. Here's an example of using the fields and methods in a template:
spec:
commit:
messageTemplate: |
Automated image update
Automation name: {{ .AutomationObject }}
Files:
{{ range $filename, $_ := .Updated.Files -}}
- {{ $filename }}
{{ end -}}
Objects:
{{ range $resource, $_ := .Updated.Objects -}}
- {{ $resource.Kind }} {{ $resource.Name }}
{{ end -}}
Images:
{{ range .Updated.Images -}}
- {{.}}
{{ end -}}
With template functions, it is possible to manipulate and transform the supplied data in order to generate more complex commit messages.
kind: ImageUpdateAutomation
metadata:
name: flux-system
spec:
git:
commit:
messageTemplate: |
Automated image update
Automation name: {{ .AutomationObject }}
Files:
{{ range $filename, $_ := .Updated.Files -}}
- {{ $filename }}
{{ end -}}
Objects:
{{ range $resource, $_ := .Updated.Objects -}}
- {{ $resource.Kind | lower }} {{ $resource.Name | lower }}
{{ end -}}
Images:
{{ range $image, $_ := .Updated.Images -}}
{{ if contains "1.0.0" $image -}}
- {{ $image }}
{{ else -}}
[skip ci] wrong image
{{ end -}}
{{ end -}}
author:
email: [email protected]
name: fluxcdbot
There are over 70 available functions. Some of them are defined by the Go template language itself. Most of the others are part of the Sprig template library.
The optional push
field defines how commits are pushed to the origin.
// PushSpec specifies how and where to push commits.
type PushSpec struct {
// Branch specifies that commits should be pushed to the branch
// named. The branch is created using `.spec.checkout.branch` as the
// starting point, if it doesn't already exist.
// +optional
Branch string `json:"branch,omitempty"`
// Refspec specifies the Git Refspec to use for a push operation.
// If both Branch and Refspec are provided, then the commit is pushed
// to the branch and also using the specified refspec.
// For more details about Git Refspecs, see:
// https://git-scm.com/book/en/v2/Git-Internals-The-Refspec
// +optional
Refspec string `json:"refspec,omitempty"`
// Options specifies the push options that are sent to the Git
// server when performing a push operation. For details, see:
// https://git-scm.com/docs/git-push#Documentation/git-push.txt---push-optionltoptiongt
Options map[string]string `json:"options,omitempty"`
}
If .push
is not present, commits are made on the branch given in .spec.git.checkout.branch
and
pushed to the same branch at the origin. If .spec.git.checkout
is not present, it will fall back
to the branch given in the GitRepository
referenced by .spec.sourceRef
. If none of these yield a
branch name, the automation will fail.
If .push.refspec
is present, the refspec specified is used to perform the push operation.
An example of a valid refspec is refs/heads/branch:refs/heads/branch
. This allows users to
push to an arbitary destination reference.
If .push.branch
is present, the specified branch is pushed to at the origin. The branch
will be created locally if it does not already exist, starting from the checkout branch. If it does
already exist, it will be overwritten with the cloned version plus the changes made by the
controller. Alternatively, force push can be disabled by starting the controller with --feature-gates=GitForcePushBranch=false
,
in which case the updates will be calculated on top of any commits already on the push branch.
Note that without force push in push branches, if the target branch is stale, the controller may not
be able to conclude the operation and will consistently fail until the branch is either deleted or
refreshed.
If both .push.refspec
and .push.branch
are specified, then the reconciler will perform
two push operations, one to the specified branch and another using the specified refspec.
This is particularly useful for working with Gerrit servers. For more information about this,
please refer to the Gerrit section.
Note: If both .push.refspec
and .push.branch
are essentially equal to
each other (for e.g.: .push.refspec: refs/heads/main:refs/heads/main
and
.push.branch: main
), then the reconciler might fail to perform the second push
operation and error out with an already up-to-date
error.
In the following snippet, updates will be pushed as commits to the branch auto
, and when that
branch does not exist at the origin, it will be created locally starting from the branch main
, and
pushed:
spec:
git:
checkout:
ref:
branch: main
push:
branch: auto
In the following snippet, updates and commits will be made on the main
branch locally.
The commits will be then pushed using the refs/heads/main:refs/heads/auto
refspec:
spec:
git:
checkout:
ref:
branch: main
push:
refspec: refs/heads/main:refs/heads/auto
To specify the push options
to be sent to the upstream Git server, use .push.options
. These options can be
used to perform operations as a result of the push. For example, using the below
push options will open a GitLab Merge Request to the release
branch
automatically with the commit the controller pushed to the dev
branch:
spec:
git:
push:
branch: dev
options:
merge_request.create: ""
merge_request.target: release
Gerrit operates differently from a
standard Git server. Rather than sending individual commits to a branch,
all changes are bundled into a single commit. This commit requires a distinct
identifier separate from the commit SHA. Additionally, instead of initiating
a Pull Request between branches, the commit is pushed using a refspec:
HEAD:refs/for/main
.
As the image-automation-controller is primarily designed to work with standard Git servers, these special characteristics necessitate a few workarounds. The following is an example configuration that works well with Gerrit:
spec:
git:
checkout:
ref:
branch: main
commit:
author:
email: flux@localdomain
name: flux
messageTemplate: |
Perform automatic image update
Automation name: {{ .AutomationObject }}
Files:
{{ range $filename, $_ := .Updated.Files -}}
- {{ $filename }}
{{ end }}
Objects:
{{ range $resource, $_ := .Updated.Objects -}}
- {{ $resource.Kind }} {{ $resource.Name }}
{{ end }}
Images:
{{ range .Updated.Images -}}
- {{ . }}
{{ end }}
{{- $ChangeId := .AutomationObject -}}
{{- $ChangeId = printf "%s%s" $ChangeId ( .Updated.Files | toString ) -}}
{{- $ChangeId = printf "%s%s" $ChangeId ( .Updated.Objects | toString ) -}}
{{- $ChangeId = printf "%s%s" $ChangeId ( .Updated.Images | toString ) }}
Change-Id: {{ printf "I%s" ( sha256sum $ChangeId | trunc 40 ) }}
push:
branch: auto
refspec: refs/heads/auto:refs/heads/main
This instructs the image-automation-controller to clone the repository using the
main
branch but execute its update logic and commit with the provided message
template on the auto
branch. Commits are then pushed to the auto
branch,
followed by pushing the HEAD
of the auto
branch to the HEAD
of the remote
main
branch. The message template ensures the inclusion of a Change-Id
at the bottom of the commit message.
The initial branch push aims to prevent multiple
Patch Sets.
If we exclude .push.branch
and only specify
.push.refspec: refs/heads/main:refs/heads/main
, the desired Change
can be created as intended. However, when the controller freshly clones the
main
branch while a Change is open, it executes its update logic on main
,
leading to new commits being pushed with the same changes to the existing open
Change. Specifying .push.branch
circumvents this by instructing the controller
to apply the update logic to the auto
branch, already containing the desired
commit. This approach is also recommended in the
Gerrit documentation.
Another thing to note is the syntax of .push.refspec
. Instead of it being
HEAD:refs/for/main
, commonly used by Gerrit users, we specify the full
refname refs/heads/auto
in the source part of the refpsec.
Note: A known limitation of using the image-automation-controller with Gerrit involves handling multiple concurrent Changes. This is due to the calculation of the Change-Id, relying on factors like file names and image tags. If the controller introduces a new file or modifies a previously updated image tag to a different one, it leads to a distinct Change-Id for the commit. Consequently, this action will trigger the creation of an additional Change, even when an existing Change containing outdated modifications remains open.
The .spec.update
field specifies how to carry out updates on the git repository. There is one
strategy possible at present -- {strategy: Setters}
. This field may be left empty, to default to
that value.
// UpdateStrategyName is the type for names that go in
// .update.strategy. NB the value in the const immediately below.
// +kubebuilder:validation:Enum=Setters
type UpdateStrategyName string
const (
// UpdateStrategySetters is the name of the update strategy that
// uses kyaml setters. NB the value in the enum annotation for the
// type, above.
UpdateStrategySetters UpdateStrategyName = "Setters"
)
// UpdateStrategy is a union of the various strategies for updating
// the Git repository. Parameters for each strategy (if any) can be
// inlined here.
type UpdateStrategy struct {
// Strategy names the strategy to be used.
// +required
// +kubebuilder:default=Setters
Strategy UpdateStrategyName `json:"strategy"`
// Path to the directory containing the manifests to be updated.
// Defaults to 'None', which translates to the root path
// of the GitRepositoryRef.
// +optional
Path string `json:"path,omitempty"`
}
Setters strategy
At present, there is one strategy: "Setters". This uses field markers referring to image policies, as described in the image automation guide.
The status of an ImageUpdateAutomation
object records the result of the last automation run.
// ImageUpdateAutomationStatus defines the observed state of ImageUpdateAutomation
type ImageUpdateAutomationStatus struct {
// LastAutomationRunTime records the last time the controller ran
// this automation through to completion (even if no updates were
// made).
// +optional
LastAutomationRunTime *metav1.Time `json:"lastAutomationRunTime,omitempty"`
// LastPushCommit records the SHA1 of the last commit made by the
// controller, for this automation object
// +optional
LastPushCommit string `json:"lastPushCommit,omitempty"`
// LastPushTime records the time of the last pushed change.
// +optional
LastPushTime *metav1.Time `json:"lastPushTime,omitempty"`
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
meta.ReconcileRequestStatus `json:",inline"`
}
The lastAutomationRunTime
gives the time of the last automation run, whether or not it made a
commit. The lastPushCommit
field records the SHA1 hash of the last commit pushed to the origin git
repository, and the lastPushTime
gives the time that push occurred.
There is one condition maintained by the controller, which is the usual ReadyCondition
condition. This will be recorded as True
when automation has run without errors, whether or not it
resulted in a commit.
For the most part, v1alpha2
rearranges the API types to provide for future extension. Here are the
differences, and where each v1alpha1
field goes. A full example appears after the table.
v1alpha1 field |
change in v1alpha2 |
---|---|
.spec.checkout | moved to .spec.git.checkout , and optional |
gitRepositoryRef is now .spec.sourceRef |
|
branch is now ref , and optional |
|
.spec.commit | moved to .spec.git.commit |
authorName and authorEmail now author.name and author.email |
|
.spec.push | moved to .spec.git.push |
This example shows the steps to rewrite a v1alpha1 ImageUpdateAutomation YAML to be a v1alpha2 YAML.
This is the v1alpha1 original:
apiVersion: image.toolkit.fluxcd.io/v1alpha1
kind: ImageUpdateAutomation
spec:
checkout:
gitRepositoryRef:
name: auto-repo
branch: main
interval: 5m
# omit suspend, which has not changed
update:
strategy: Setters
path: ./app
commit:
authorName: fluxbot
authorEmail: [email protected]
messageTemplate: |
An automated update from FluxBot
[ci skip]
signingKey:
secretRef:
name: git-pgp
push:
branch: auto
Change the API version
The API version is now image.toolkit.fluxcd.io/v1beta1
:
apiVersion: image.toolkit.fluxcd.io/v1alpha1
# becomes
apiVersion: image.toolkit.fluxcd.io/v1beta1
Move and adapt .spec.checkout.gitRepositoryRef
to .spec.sourceRef
and .spec.git.checkout
The reference to a GitRepository
object has moved to the field sourceRef
. The checkout
field
moves under the git
key, with the branch to checkout in a structure under ref
.
spec:
checkout:
gitRepositoryRef:
name: auto-repo
branch:
main
# becomes
spec:
sourceRef:
kind: GitRepository # the default, but good practice to be explicit here
name: auto-repo
git:
checkout:
ref:
branch: main
Note that .spec.git.checkout
is now optional. If not supplied, the .spec.ref
field from the
GitRepository
object is used as the checkout for updates.
Move and adapt .spec.commit
to spec.git.commit
The commit
field also moves under the git
key, and the author is a structure rather than two
fields.
spec:
commit:
authorName: fluxbot
authorEmail: [email protected]
messageTemplate: |
An automated update from FluxBot
[ci skip]
signingKey:
secretRef:
name: git-pgp
# becomes
spec:
git:
commit:
author:
name: fluxbot
email: [email protected]
messageTemplate: |
An automated update from FluxBot
[ci skip]
signingKey:
secretRef:
name: git-pgp
Move .spec.push
to .spec.git.push
The field push
moves under the git
key.
spec:
push:
branch: auto
# becomes
spec:
git:
push:
branch: auto
Overall result
The final YAML looks like this:
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageUpdateAutomation
spec:
sourceRef: # moved from `.spec.checkout`
kind: GitRepository
name: auto-repo
interval: 5m
# omit suspend, which has not changed
update:
strategy: Setters
path: ./app
git:
checkout: # moved under `git`, loses `gitRepositoryRef`
ref:
branch: main # moved into `ref` struct
commit: # moved under `git`
author:
name: fluxbot # moved from `authorName`
email: [email protected] # moved from `authorEmail`
messageTemplate: |
An automated update from FluxBot
[ci skip]
signingKey:
secretRef:
name: git-pgp
push: # moved under `git`
branch: auto