Skip to content

Commit f3cce33

Browse files
authored
fix: condition updater (#93)
* fix bug in condition updater (wrongfully updated condition) * enforce reason * add GenerateCreateConditionFunc function to controller package * feat: release v0.13.1
1 parent 0810718 commit f3cce33

File tree

6 files changed

+87
-9
lines changed

6 files changed

+87
-9
lines changed

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
v0.13.0-dev
1+
v0.13.1

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ require (
1111
github.com/onsi/ginkgo v1.16.5
1212
github.com/onsi/ginkgo/v2 v2.23.4
1313
github.com/onsi/gomega v1.37.0
14-
github.com/openmcp-project/controller-utils/api v0.13.0
14+
github.com/openmcp-project/controller-utils/api v0.13.1
1515
github.com/spf13/pflag v1.0.6
1616
github.com/stretchr/testify v1.10.0
1717
go.uber.org/zap v1.27.0

pkg/conditions/updater.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ func (c *conditionUpdater) WithEventRecorder(recorder record.EventRecorder, verb
8888
// All fields of the condition are updated with the values given in the arguments, but the condition's LastTransitionTime is only updated (with the timestamp contained in the receiver struct) if the status changed.
8989
// Returns the receiver for easy chaining.
9090
func (c *conditionUpdater) UpdateCondition(conType string, status metav1.ConditionStatus, observedGeneration int64, reason, message string) *conditionUpdater {
91+
if reason == "" {
92+
// the metav1.Condition type requires a reason, so let's add a dummy if none is given
93+
reason = conType + string(status)
94+
}
9195
con := metav1.Condition{
9296
Type: conType,
9397
Status: status,
@@ -96,11 +100,6 @@ func (c *conditionUpdater) UpdateCondition(conType string, status metav1.Conditi
96100
ObservedGeneration: observedGeneration,
97101
LastTransitionTime: c.Now,
98102
}
99-
old, ok := c.conditions[conType]
100-
if ok && old.Status == con.Status {
101-
// update LastTransitionTime only if status changed
102-
con.LastTransitionTime = old.LastTransitionTime
103-
}
104103
c.updates[conType] = status
105104
c.conditions[conType] = con
106105
return c
@@ -134,7 +133,13 @@ func (c *conditionUpdater) RemoveCondition(conType string) *conditionUpdater {
134133
// The conditions are returned sorted by their type.
135134
// The second return value indicates whether the condition list has actually changed.
136135
func (c *conditionUpdater) Conditions() ([]metav1.Condition, bool) {
137-
res := c.updatedConditions()
136+
res := collections.ProjectSlice(c.updatedConditions(), func(con metav1.Condition) metav1.Condition {
137+
if c.original[con.Type].Status == con.Status {
138+
// if the status has not changed, reset the LastTransitionTime to the original value
139+
con.LastTransitionTime = c.original[con.Type].LastTransitionTime
140+
}
141+
return con
142+
})
138143
slices.SortStableFunc(res, func(a, b metav1.Condition) int {
139144
return strings.Compare(a.Type, b.Type)
140145
})

pkg/conditions/updater_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,16 @@ var _ = Describe("Conditions", func() {
161161
Expect(newCon.LastTransitionTime.After(oldCon.LastTransitionTime.Time)).To(BeTrue())
162162
})
163163

164+
It("should not change the last transition time if the same condition is updated multiple times with the last update setting it to the original value again", func() {
165+
cons := testConditionSet()
166+
oldCon := conditions.GetCondition(cons, "true")
167+
updater := conditions.ConditionUpdater(cons, false)
168+
updated, changed := updater.UpdateCondition(oldCon.Type, invert(oldCon.Status), oldCon.ObservedGeneration+1, "newReason", "newMessage").
169+
UpdateCondition(oldCon.Type, oldCon.Status, oldCon.ObservedGeneration, oldCon.Reason, oldCon.Message).Conditions()
170+
Expect(changed).To(BeFalse())
171+
Expect(updated).To(ConsistOf(cons))
172+
})
173+
164174
It("should sort the conditions by type", func() {
165175
cons := []metav1.Condition{
166176
TestConditionFromValues("c", conditions.FromBool(true), 0, "reason", "message", metav1.Now()).ToCondition(),

pkg/controller/status_updater.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,11 @@ func (s *statusUpdater[Obj]) UpdateStatus(ctx context.Context, c client.Client,
231231
}
232232
cu.Now = now
233233
for _, con := range rr.Conditions {
234-
cu.UpdateConditionFromTemplate(con)
234+
gen := con.ObservedGeneration
235+
if gen == 0 {
236+
gen = rr.Object.GetGeneration()
237+
}
238+
cu.UpdateCondition(con.Type, con.Status, gen, con.Reason, con.Message)
235239
}
236240
newCons, _ := cu.Record(rr.Object).Conditions()
237241
SetField(status, s.fieldNames[STATUS_FIELD_CONDITIONS], newCons)
@@ -366,3 +370,21 @@ type ReconcileResult[Obj client.Object] struct {
366370
// The lastTransition timestamp of the condition will be overwritten with the current time while updating.
367371
Conditions []metav1.Condition
368372
}
373+
374+
// GenerateCreateConditionFunc returns a function that can be used to add a condition to the given ReconcileResult.
375+
// If the ReconcileResult's Object is not nil, the condition's ObservedGeneration is set to the object's generation.
376+
func GenerateCreateConditionFunc[Obj client.Object](rr *ReconcileResult[Obj]) func(conType string, status metav1.ConditionStatus, reason, message string) {
377+
var gen int64 = 0
378+
if any(rr.Object) != nil {
379+
gen = rr.Object.GetGeneration()
380+
}
381+
return func(conType string, status metav1.ConditionStatus, reason, message string) {
382+
rr.Conditions = append(rr.Conditions, metav1.Condition{
383+
Type: conType,
384+
Status: status,
385+
ObservedGeneration: gen,
386+
Reason: reason,
387+
Message: message,
388+
})
389+
}
390+
}

pkg/controller/status_updater_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,47 @@ var _ = Describe("Status Updater", func() {
194194
}
195195
})
196196

197+
Context("GenerateCreateConditionFunc", func() {
198+
199+
It("should add the condition to the given ReconcileResult", func() {
200+
rr := controller.ReconcileResult[*CustomObject]{
201+
Object: &CustomObject{
202+
ObjectMeta: metav1.ObjectMeta{
203+
Name: "test",
204+
Namespace: "default",
205+
Generation: 15,
206+
},
207+
},
208+
Conditions: dummyConditions(),
209+
}
210+
createCon := controller.GenerateCreateConditionFunc(&rr)
211+
createCon("TestConditionFoo", metav1.ConditionTrue, "TestReasonFoo", "TestMessageFoo")
212+
Expect(rr.Conditions).To(ConsistOf(
213+
MatchCondition(TestConditionFromCondition(dummyConditions()[0])),
214+
MatchCondition(TestConditionFromCondition(dummyConditions()[1])),
215+
MatchCondition(TestConditionFromValues("TestConditionFoo", metav1.ConditionTrue, 15, "TestReasonFoo", "TestMessageFoo", metav1.Time{})),
216+
))
217+
})
218+
219+
It("should create the condition list, if it is nil", func() {
220+
rr := controller.ReconcileResult[*CustomObject]{
221+
Object: &CustomObject{
222+
ObjectMeta: metav1.ObjectMeta{
223+
Name: "test",
224+
Namespace: "default",
225+
Generation: 15,
226+
},
227+
},
228+
}
229+
createCon := controller.GenerateCreateConditionFunc(&rr)
230+
createCon("TestConditionFoo", metav1.ConditionTrue, "TestReasonFoo", "TestMessageFoo")
231+
Expect(rr.Conditions).To(ConsistOf(
232+
MatchCondition(TestConditionFromValues("TestConditionFoo", metav1.ConditionTrue, 15, "TestReasonFoo", "TestMessageFoo", metav1.Time{})),
233+
))
234+
})
235+
236+
})
237+
197238
})
198239

199240
func preconfiguredStatusUpdaterBuilder() *controller.StatusUpdaterBuilder[*CustomObject] {

0 commit comments

Comments
 (0)