Skip to content

Commit d606498

Browse files
adityathebemoshloop
authored andcommitted
feat: make reconcile conditions generic via StatusConditioner
Replace ReconcileStatusSetter with StatusConditioner so CRDs only expose status conditions and Kopper manages Ready condition transitions. Add constants for condition type and reasons: Ready, Synced, PersistFailed, DeleteFailed.
1 parent 97b0e1f commit d606498

1 file changed

Lines changed: 40 additions & 10 deletions

File tree

reconciler.go

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import (
1212
"github.com/jackc/pgx/v5/pgconn"
1313
"github.com/samber/lo"
1414
apiErrors "k8s.io/apimachinery/pkg/api/errors"
15+
k8smeta "k8s.io/apimachinery/pkg/api/meta"
16+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1517
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1618
"k8s.io/apimachinery/pkg/runtime"
1719
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -28,12 +30,18 @@ type StatusPatchGenerator interface {
2830
GenerateStatusPatch(previousState runtime.Object) client.Patch
2931
}
3032

31-
// ReconcileStatusSetter allows a CRD to update its own status when a reconcile
32-
// operation fails. Kopper will call the appropriate method and write the status
33-
// back to Kubernetes automatically.
34-
type ReconcileStatusSetter interface {
35-
SetUpsertErrorStatus(err error)
36-
SetDeleteErrorStatus(err error)
33+
const (
34+
ReadyConditionType = "Ready"
35+
36+
ReasonSynced = "Synced"
37+
ReasonPersistFailed = "PersistFailed"
38+
ReasonDeleteFailed = "DeleteFailed"
39+
)
40+
41+
// StatusConditioner allows a CRD to expose its status conditions slice so
42+
// Kopper can set generic reconcile conditions.
43+
type StatusConditioner interface {
44+
GetStatusConditions() *[]metav1.Condition
3745
}
3846

3947
// OnUpsertFunc is a function that is called when a resource is created or updated
@@ -106,6 +114,29 @@ func (r *Reconciler[T, PT]) updateStatus(ctx gocontext.Context, resourceName str
106114
return nil
107115
}
108116

117+
func (r *Reconciler[T, PT]) setCondition(obj PT, status metav1.ConditionStatus, reason, message string) bool {
118+
conditioner, ok := any(obj).(StatusConditioner)
119+
if !ok {
120+
return false
121+
}
122+
123+
conditions := conditioner.GetStatusConditions()
124+
if conditions == nil {
125+
return false
126+
}
127+
128+
k8smeta.SetStatusCondition(conditions, metav1.Condition{
129+
Type: ReadyConditionType,
130+
Status: status,
131+
Reason: reason,
132+
Message: message,
133+
ObservedGeneration: obj.GetGeneration(),
134+
LastTransitionTime: metav1.Now(),
135+
})
136+
137+
return true
138+
}
139+
109140
func (r *Reconciler[T, PT]) Reconcile(ctx gocontext.Context, req ctrl.Request) (ctrl.Result, error) {
110141
raw := &unstructured.Unstructured{}
111142
raw.SetGroupVersionKind(r.gvk)
@@ -133,8 +164,7 @@ func (r *Reconciler[T, PT]) Reconcile(ctx gocontext.Context, req ctrl.Request) (
133164
logger.V(2).Infof("[kopper] deleting %s", resourceName)
134165
if err := r.OnDeleteFunc(r.DutyContext, string(obj.GetUID())); err != nil {
135166
logger.Errorf("[kopper] failed to delete %s: %v", resourceName, err)
136-
if setter, ok := any(obj).(ReconcileStatusSetter); ok {
137-
setter.SetDeleteErrorStatus(err)
167+
if r.setCondition(obj, metav1.ConditionFalse, ReasonDeleteFailed, err.Error()) {
138168
if statusErr := r.updateStatus(ctx, resourceName, obj, original); statusErr != nil {
139169
err = errors.Join(err, fmt.Errorf("failed to update status for %s: %w", resourceName, statusErr))
140170
}
@@ -170,15 +200,15 @@ func (r *Reconciler[T, PT]) Reconcile(ctx gocontext.Context, req ctrl.Request) (
170200
}
171201

172202
logger.Errorf("[kopper] failed to upsert %s: %v", resourceName, err)
173-
if setter, ok := any(obj).(ReconcileStatusSetter); ok {
174-
setter.SetUpsertErrorStatus(err)
203+
if r.setCondition(obj, metav1.ConditionFalse, ReasonPersistFailed, err.Error()) {
175204
if statusErr := r.updateStatus(ctx, resourceName, obj, original); statusErr != nil {
176205
err = errors.Join(err, fmt.Errorf("failed to update status for %s: %w", resourceName, statusErr))
177206
}
178207
}
179208
return ctrl.Result{Requeue: true, RequeueAfter: 2 * time.Minute}, err
180209
}
181210

211+
r.setCondition(obj, metav1.ConditionTrue, ReasonSynced, "")
182212
if err := r.updateStatus(ctx, resourceName, obj, original); err != nil {
183213
return ctrl.Result{Requeue: true, RequeueAfter: 2 * time.Minute}, err
184214
}

0 commit comments

Comments
 (0)