Skip to content

Commit 24d539d

Browse files
authored
Merge pull request #53 from fluxcd/conditions-status
Make controller update ready condition
2 parents 24bbf91 + 6a0d528 commit 24d539d

File tree

9 files changed

+183
-44
lines changed

9 files changed

+183
-44
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ CRD_OPTIONS ?= crd:crdVersions=v1
55

66
# Version of the Toolkit from which to get CRDs. Change this if you
77
# bump the go module version.
8-
TOOLKIT_VERSION:=v0.2.2
8+
TOOLKIT_VERSION:=v0.3.0
99

1010
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
1111
ifeq (,$(shell go env GOBIN))

api/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/fluxcd/image-automation-controller/api
33
go 1.15
44

55
require (
6+
github.com/fluxcd/pkg/apis/meta v0.4.0
67
k8s.io/api v0.19.3
78
k8s.io/apimachinery v0.19.3
89
sigs.k8s.io/controller-runtime v0.6.3

api/go.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLi
6161
github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
6262
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
6363
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
64+
github.com/fluxcd/pkg v0.0.4 h1:fMA6GG3FTSBFDrlB026gQJhxj4xVuvcvwP3rhX/Yqrw=
65+
github.com/fluxcd/pkg/apis/meta v0.4.0 h1:JChqB9GGgorW9HWKxirTVV0rzrcLyzBaVjinmqZ0iHA=
66+
github.com/fluxcd/pkg/apis/meta v0.4.0/go.mod h1:wOzQQx8CdtUQCGaLzqGu4QgnNxYkI6/wvdvlovxWhF0=
6467
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
6568
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
6669
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=

api/v1alpha1/imageupdateautomation_types.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ package v1alpha1
1919
import (
2020
corev1 "k8s.io/api/core/v1"
2121
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
22+
23+
"github.com/fluxcd/pkg/apis/meta"
2224
)
2325

2426
// ImageUpdateAutomationSpec defines the desired state of ImageUpdateAutomation
@@ -92,6 +94,27 @@ type ImageUpdateAutomationStatus struct {
9294
// made).
9395
// +optional
9496
LastAutomationRunTime *metav1.Time `json:"lastAutomationRunTime,omitempty"`
97+
// +optional
98+
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
99+
// +optional
100+
Conditions []metav1.Condition `json:"conditions,omitempty"`
101+
}
102+
103+
const (
104+
// GitNotAvailableReason is used for ConditionReady when the
105+
// automation run cannot proceed because the git repository is
106+
// missing or cannot be cloned.
107+
GitNotAvailableReason = "GitRepositoryNotAvailable"
108+
// NoStrategyReason is used for ConditionReady when the automation
109+
// run cannot proceed because there is no update strategy given in
110+
// the spec.
111+
NoStrategyReason = "MissingUpdateStrategy"
112+
)
113+
114+
// SetImageUpdateAutomationReadiness sets the ready condition with the given status, reason and message.
115+
func SetImageUpdateAutomationReadiness(auto *ImageUpdateAutomation, status metav1.ConditionStatus, reason, message string) {
116+
auto.Status.ObservedGeneration = auto.ObjectMeta.Generation
117+
meta.SetResourceCondition(auto, meta.ReadyCondition, status, reason, message)
95118
}
96119

97120
// +kubebuilder:object:root=true
@@ -107,6 +130,10 @@ type ImageUpdateAutomation struct {
107130
Status ImageUpdateAutomationStatus `json:"status,omitempty"`
108131
}
109132

133+
func (auto *ImageUpdateAutomation) GetStatusConditions() *[]metav1.Condition {
134+
return &auto.Status.Conditions
135+
}
136+
110137
// +kubebuilder:object:root=true
111138

112139
// ImageUpdateAutomationList contains a list of ImageUpdateAutomation

api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/image.toolkit.fluxcd.io_imageupdateautomations.yaml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,12 +112,84 @@ spec:
112112
description: ImageUpdateAutomationStatus defines the observed state of
113113
ImageUpdateAutomation
114114
properties:
115+
conditions:
116+
items:
117+
description: "Condition contains details for one aspect of the current
118+
state of this API Resource. --- This struct is intended for direct
119+
use as an array at the field path .status.conditions. For example,
120+
type FooStatus struct{ // Represents the observations of a
121+
foo's current state. // Known .status.conditions.type are:
122+
\"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type
123+
\ // +patchStrategy=merge // +listType=map // +listMapKey=type
124+
\ Conditions []metav1.Condition `json:\"conditions,omitempty\"
125+
patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`
126+
\n // other fields }"
127+
properties:
128+
lastTransitionTime:
129+
description: lastTransitionTime is the last time the condition
130+
transitioned from one status to another. This should be when
131+
the underlying condition changed. If that is not known, then
132+
using the time when the API field changed is acceptable.
133+
format: date-time
134+
type: string
135+
message:
136+
description: message is a human readable message indicating
137+
details about the transition. This may be an empty string.
138+
maxLength: 32768
139+
type: string
140+
observedGeneration:
141+
description: observedGeneration represents the .metadata.generation
142+
that the condition was set based upon. For instance, if .metadata.generation
143+
is currently 12, but the .status.conditions[x].observedGeneration
144+
is 9, the condition is out of date with respect to the current
145+
state of the instance.
146+
format: int64
147+
minimum: 0
148+
type: integer
149+
reason:
150+
description: reason contains a programmatic identifier indicating
151+
the reason for the condition's last transition. Producers
152+
of specific condition types may define expected values and
153+
meanings for this field, and whether the values are considered
154+
a guaranteed API. The value should be a CamelCase string.
155+
This field may not be empty.
156+
maxLength: 1024
157+
minLength: 1
158+
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
159+
type: string
160+
status:
161+
description: status of the condition, one of True, False, Unknown.
162+
enum:
163+
- "True"
164+
- "False"
165+
- Unknown
166+
type: string
167+
type:
168+
description: type of condition in CamelCase or in foo.example.com/CamelCase.
169+
--- Many .condition.type values are consistent across resources
170+
like Available, but because arbitrary conditions can be useful
171+
(see .node.status.conditions), the ability to deconflict is
172+
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
173+
maxLength: 316
174+
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
175+
type: string
176+
required:
177+
- lastTransitionTime
178+
- message
179+
- reason
180+
- status
181+
- type
182+
type: object
183+
type: array
115184
lastAutomationRunTime:
116185
description: LastAutomationRunTime records the last time the controller
117186
ran this automation through to completion (even if no updates were
118187
made).
119188
format: date-time
120189
type: string
190+
observedGeneration:
191+
format: int64
192+
type: integer
121193
type: object
122194
type: object
123195
served: true

controllers/imageupdateautomation_controller.go

Lines changed: 49 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ import (
4444
"sigs.k8s.io/controller-runtime/pkg/source"
4545

4646
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1alpha1"
47+
"github.com/fluxcd/pkg/apis/meta"
4748
"github.com/fluxcd/pkg/runtime/events"
49+
"github.com/fluxcd/pkg/runtime/predicates"
4850
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
4951
"github.com/fluxcd/source-controller/pkg/git"
5052

@@ -78,22 +80,36 @@ type ImageUpdateAutomationReconciler struct {
7880
func (r *ImageUpdateAutomationReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
7981
ctx := context.Background()
8082
log := r.Log.WithValues("imageupdateautomation", req.NamespacedName)
83+
now := time.Now()
8184

8285
var auto imagev1.ImageUpdateAutomation
8386
if err := r.Get(ctx, req.NamespacedName, &auto); err != nil {
8487
return ctrl.Result{}, client.IgnoreNotFound(err)
8588
}
8689

90+
// failWithError is a helper for bailing on the reconciliation.
91+
failWithError := func(err error) (ctrl.Result, error) {
92+
r.event(auto, events.EventSeverityError, err.Error())
93+
imagev1.SetImageUpdateAutomationReadiness(&auto, metav1.ConditionFalse, meta.ReconciliationFailedReason, err.Error())
94+
if err := r.Status().Update(ctx, &auto); err != nil {
95+
log.Error(err, "failed to reconcile")
96+
}
97+
return ctrl.Result{Requeue: true}, err
98+
}
99+
87100
// get the git repository object so it can be checked out
88101
var origin sourcev1.GitRepository
89102
originName := types.NamespacedName{
90103
Name: auto.Spec.Checkout.GitRepositoryRef.Name,
91104
Namespace: auto.GetNamespace(),
92105
}
93106
if err := r.Get(ctx, originName, &origin); err != nil {
94-
// TODO status
95107
if client.IgnoreNotFound(err) == nil {
108+
imagev1.SetImageUpdateAutomationReadiness(&auto, metav1.ConditionFalse, imagev1.GitNotAvailableReason, "referenced git repository is missing")
96109
log.Error(err, "referenced git repository does not exist")
110+
if err := r.Status().Update(ctx, &auto); err != nil {
111+
return ctrl.Result{Requeue: true}, err
112+
}
97113
return ctrl.Result{}, nil // and assume we'll hear about it when it arrives
98114
}
99115
return ctrl.Result{}, err
@@ -103,22 +119,20 @@ func (r *ImageUpdateAutomationReconciler) Reconcile(req ctrl.Request) (ctrl.Resu
103119

104120
tmp, err := ioutil.TempDir("", fmt.Sprintf("%s-%s", originName.Namespace, originName.Name))
105121
if err != nil {
106-
// TODO status
107-
return ctrl.Result{}, err
122+
return failWithError(err)
108123
}
109124
defer os.RemoveAll(tmp)
110125

111126
// FIXME use context with deadline for at least the following ops
112127

113128
access, err := r.getRepoAccess(ctx, &origin)
114129
if err != nil {
115-
return ctrl.Result{}, err
130+
return failWithError(err)
116131
}
117132

118133
var repo *gogit.Repository
119134
if repo, err = cloneInto(ctx, access, auto.Spec.Checkout.Branch, tmp); err != nil {
120-
// TODO status
121-
return ctrl.Result{}, err
135+
return failWithError(err)
122136
}
123137

124138
log.V(debug).Info("cloned git repository", "gitrepository", originName, "branch", auto.Spec.Checkout.Branch, "working", tmp)
@@ -131,59 +145,53 @@ func (r *ImageUpdateAutomationReconciler) Reconcile(req ctrl.Request) (ctrl.Resu
131145
// could be filtered by the automation object).
132146
var policies imagev1_reflect.ImagePolicyList
133147
if err := r.List(ctx, &policies, &client.ListOptions{Namespace: req.NamespacedName.Namespace}); err != nil {
134-
return ctrl.Result{}, err
148+
return failWithError(err)
135149
}
136150

137151
if err := updateAccordingToSetters(ctx, tmp, policies.Items); err != nil {
138-
r.event(auto, events.EventSeverityError, err.Error())
139-
return ctrl.Result{}, err
152+
return failWithError(err)
140153
}
141154
default:
142155
log.Info("no update strategy given in the spec")
143156
// no sense rescheduling until this resource changes
144-
return ctrl.Result{}, nil
157+
r.event(auto, events.EventSeverityInfo, "no update strategy in spec, failing trivially")
158+
imagev1.SetImageUpdateAutomationReadiness(&auto, metav1.ConditionFalse, imagev1.NoStrategyReason, "no update strategy is given for object")
159+
err := r.Status().Update(ctx, &auto)
160+
return ctrl.Result{}, err
145161
}
146162

147163
log.V(debug).Info("ran updates to working dir", "working", tmp)
148164

149-
var commitMade bool
165+
var statusMessage string
150166

151167
if rev, err := commitAllAndPush(ctx, repo, access, &auto.Spec.Commit); err != nil {
152168
if err == errNoChanges {
153-
log.Info("no changes made in working directory; no commit")
169+
r.event(auto, events.EventSeverityInfo, "no updates made")
170+
log.V(debug).Info("no changes made in working directory; no commit")
171+
statusMessage = "no updates made"
154172
} else {
155-
r.event(auto, events.EventSeverityError, err.Error())
156-
return ctrl.Result{}, err
173+
return failWithError(err)
157174
}
158175
} else {
159-
commitMade = true
160176
r.event(auto, events.EventSeverityInfo, "committed and pushed change "+rev)
161-
log.V(debug).Info("pushed commit to origin", "revision", rev)
177+
log.Info("pushed commit to origin", "revision", rev)
178+
statusMessage = "committed and pushed " + rev
162179
}
163180

164-
// The status is not updated unless a commit was made, OR it's
165-
// been at least interval since the last run (in which case,
166-
// assume this is a periodic run). This is so there's a fixed
167-
// point -- otherwise, the fact of the status change would mean it
168-
// gets queued again.
169-
170-
now := time.Now()
171-
interval := intervalOrDefault(&auto)
172-
sinceLast := durationSinceLastRun(&auto, now)
173-
174-
when := interval
175-
176-
if commitMade || sinceLast >= interval {
177-
auto.Status.LastAutomationRunTime = &metav1.Time{Time: now}
178-
if err = r.Status().Update(ctx, &auto); err != nil {
179-
return ctrl.Result{}, err
180-
}
181-
} else {
182-
// requeue for the remainder of the interval
183-
when = interval - sinceLast
181+
// Getting to here is a successful run.
182+
auto.Status.LastAutomationRunTime = &metav1.Time{Time: now}
183+
imagev1.SetImageUpdateAutomationReadiness(&auto, metav1.ConditionTrue, meta.ReconciliationSucceededReason, statusMessage)
184+
if err = r.Status().Update(ctx, &auto); err != nil {
185+
return ctrl.Result{Requeue: true}, err
184186
}
185187

186-
return ctrl.Result{RequeueAfter: when}, nil
188+
// We're either in this method because something changed, or this
189+
// object got requeued. Either way, once successful, we don't need
190+
// to see the object again until Interval has passed, or something
191+
// changes again.
192+
193+
interval := intervalOrDefault(&auto)
194+
return ctrl.Result{RequeueAfter: interval}, nil
187195
}
188196

189197
func (r *ImageUpdateAutomationReconciler) SetupWithManager(mgr ctrl.Manager) error {
@@ -199,6 +207,7 @@ func (r *ImageUpdateAutomationReconciler) SetupWithManager(mgr ctrl.Manager) err
199207

200208
return ctrl.NewControllerManagedBy(mgr).
201209
For(&imagev1.ImageUpdateAutomation{}).
210+
WithEventFilter(predicates.ChangePredicate{}).
202211
Watches(&source.Kind{Type: &sourcev1.GitRepository{}},
203212
&handler.EnqueueRequestsFromMapFunc{
204213
ToRequests: handler.ToRequestsFunc(r.automationsForGitRepo),
@@ -214,9 +223,9 @@ func intervalOrDefault(auto *imagev1.ImageUpdateAutomation) time.Duration {
214223
return defaultInterval
215224
}
216225

217-
// durationUntilNextRun gives the length of time to wait before
218-
// running the automation again after a successful run, unless
219-
// something (a dependency) changes to trigger a run.
226+
// durationSinceLastRun calculates how long it's been since the last
227+
// time the automation ran (which you can then use to find how long to
228+
// wait until the next run).
220229
func durationSinceLastRun(auto *imagev1.ImageUpdateAutomation, now time.Time) time.Duration {
221230
last := auto.Status.LastAutomationRunTime
222231
if last == nil {

go.mod

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ replace github.com/fluxcd/image-automation-controller/api => ./api
77
require (
88
github.com/fluxcd/image-automation-controller/api v0.0.0-00010101000000-000000000000
99
github.com/fluxcd/image-reflector-controller v0.0.0-20200810165546-c2265d9b49b9
10+
github.com/fluxcd/pkg/apis/meta v0.4.0
1011
github.com/fluxcd/pkg/gittestserver v0.0.2
11-
github.com/fluxcd/pkg/runtime v0.2.0
12-
github.com/fluxcd/source-controller v0.2.2
12+
github.com/fluxcd/pkg/runtime v0.3.0
13+
github.com/fluxcd/source-controller v0.3.0
1314
// If you bump this, change TOOLKIT_VERSION in the Makefile to match
14-
github.com/fluxcd/source-controller/api v0.2.2
15+
github.com/fluxcd/source-controller/api v0.3.0
1516
github.com/go-git/go-billy/v5 v5.0.0
1617
github.com/go-git/go-git/v5 v5.1.0
1718
github.com/go-logr/logr v0.2.1

0 commit comments

Comments
 (0)