@@ -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+
109140func (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