Skip to content

Commit b6889fa

Browse files
committed
refactor: status conditions
1 parent 68f07c7 commit b6889fa

11 files changed

+170
-129
lines changed

api/v1/account_types.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,16 @@ type Account struct {
6262
Status AccountStatus `json:"status,omitempty"`
6363
}
6464

65+
// GetConditions returns the status conditions of the object.
66+
func (in *Account) GetConditions() []metav1.Condition {
67+
return in.Status.Conditions
68+
}
69+
70+
// SetConditions sets the status conditions on the object.
71+
func (in *Account) SetConditions(conditions []metav1.Condition) {
72+
in.Status.Conditions = conditions
73+
}
74+
6575
// +kubebuilder:object:root=true
6676

6777
// AccountList contains a list of Account

api/v1/condition_types.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
Copyright 2025 containeroo
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1
18+
19+
const (
20+
// ConditionTypeReady represents the fact that the object is ready.
21+
ConditionTypeReady string = "Ready"
22+
23+
// ConditionReasonReady represents the fact that the object is ready.
24+
ConditionReasonReady string = "Ready"
25+
26+
// ConditionReasonNotReady represents the fact that the object is not ready.
27+
ConditionReasonNotReady string = "NotReady"
28+
29+
// ConditionReasonFailed represents the fact that the object has failed.
30+
ConditionReasonFailed string = "Failed"
31+
)

api/v1/dnsrecord_types.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,16 @@ type DNSRecord struct {
9999
Status DNSRecordStatus `json:"status,omitempty"`
100100
}
101101

102+
// GetConditions returns the status conditions of the object.
103+
func (in *DNSRecord) GetConditions() []metav1.Condition {
104+
return in.Status.Conditions
105+
}
106+
107+
// SetConditions sets the status conditions on the object.
108+
func (in *DNSRecord) SetConditions(conditions []metav1.Condition) {
109+
in.Status.Conditions = conditions
110+
}
111+
102112
// +kubebuilder:object:root=true
103113

104114
// DNSRecordList contains a list of DNSRecord

api/v1/ip_types.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,16 @@ type IP struct {
9191
Status IPStatus `json:"status,omitempty"`
9292
}
9393

94+
// GetConditions returns the status conditions of the object.
95+
func (in *IP) GetConditions() []metav1.Condition {
96+
return in.Status.Conditions
97+
}
98+
99+
// SetConditions sets the status conditions on the object.
100+
func (in *IP) SetConditions(conditions []metav1.Condition) {
101+
in.Status.Conditions = conditions
102+
}
103+
94104
// +kubebuilder:object:root=true
95105

96106
// IPList contains a list of IP

api/v1/zone_types.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,16 @@ type Zone struct {
6464
Status ZoneStatus `json:"status,omitempty"`
6565
}
6666

67+
// GetConditions returns the status conditions of the object.
68+
func (in *Zone) GetConditions() []metav1.Condition {
69+
return in.Status.Conditions
70+
}
71+
72+
// SetConditions sets the status conditions on the object.
73+
func (in *Zone) SetConditions(conditions []metav1.Condition) {
74+
in.Status.Conditions = conditions
75+
}
76+
6777
// +kubebuilder:object:root=true
6878

6979
// ZoneList contains a list of Zone

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ go 1.23.0
55
require (
66
github.com/cloudflare/cloudflare-go v0.115.0
77
github.com/fluxcd/pkg/runtime v0.53.1
8-
github.com/go-logr/logr v1.4.2
98
github.com/itchyny/gojq v0.12.17
109
github.com/prometheus/client_golang v1.20.5
1110
golang.org/x/net v0.35.0
@@ -25,6 +24,7 @@ require (
2524
github.com/fluxcd/pkg/apis/meta v1.10.0 // indirect
2625
github.com/fsnotify/fsnotify v1.8.0 // indirect
2726
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
27+
github.com/go-logr/logr v1.4.2 // indirect
2828
github.com/go-logr/zapr v1.3.0 // indirect
2929
github.com/go-openapi/jsonpointer v0.21.0 // indirect
3030
github.com/go-openapi/jsonreference v0.21.0 // indirect

internal/common/status_conditions.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
Copyright 2025 containeroo
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package common
18+
19+
import (
20+
cloudflareoperatoriov1 "github.com/containeroo/cloudflare-operator/api/v1"
21+
"github.com/containeroo/cloudflare-operator/internal/metrics"
22+
"github.com/fluxcd/pkg/runtime/conditions"
23+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24+
)
25+
26+
// SetCondition updates the Kubernetes condition status dynamically
27+
func SetCondition(to conditions.Setter, status metav1.ConditionStatus, reason, msg string) {
28+
conditions.Set(to, &metav1.Condition{
29+
Type: cloudflareoperatoriov1.ConditionTypeReady,
30+
Status: status,
31+
Reason: reason,
32+
Message: msg,
33+
})
34+
35+
updateMetrics(to, status)
36+
}
37+
38+
// updateMetrics handles updating the failure counters for each type
39+
func updateMetrics(to conditions.Setter, status metav1.ConditionStatus) {
40+
var value float64
41+
switch status {
42+
case metav1.ConditionTrue:
43+
value = 0
44+
case metav1.ConditionFalse:
45+
value = 1
46+
}
47+
48+
switch o := to.(type) {
49+
case *cloudflareoperatoriov1.Account:
50+
metrics.AccountFailureCounter.WithLabelValues(o.Name).Set(value)
51+
52+
case *cloudflareoperatoriov1.Zone:
53+
metrics.ZoneFailureCounter.WithLabelValues(o.Name, o.Spec.Name).Set(value)
54+
55+
case *cloudflareoperatoriov1.IP:
56+
metrics.IpFailureCounter.WithLabelValues(o.Name, o.Spec.Type).Set(value)
57+
58+
case *cloudflareoperatoriov1.DNSRecord:
59+
metrics.DnsRecordFailureCounter.WithLabelValues(o.Namespace, o.Name, o.Spec.Name).Set(value)
60+
}
61+
}
62+
63+
// Convenience wrappers
64+
func MarkFalse(to conditions.Setter, err error) {
65+
SetCondition(to, metav1.ConditionFalse, cloudflareoperatoriov1.ConditionReasonFailed, err.Error())
66+
}
67+
68+
func MarkTrue(to conditions.Setter, msg string) {
69+
SetCondition(to, metav1.ConditionTrue, cloudflareoperatoriov1.ConditionReasonReady, msg)
70+
}
71+
72+
func MarkUnknown(to conditions.Setter, msg string) {
73+
SetCondition(to, metav1.ConditionUnknown, cloudflareoperatoriov1.ConditionReasonNotReady, msg)
74+
}

internal/controller/account_controller.go

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ import (
2626
"github.com/cloudflare/cloudflare-go"
2727
"github.com/fluxcd/pkg/runtime/patch"
2828
corev1 "k8s.io/api/core/v1"
29-
apimeta "k8s.io/apimachinery/pkg/api/meta"
30-
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3129
"sigs.k8s.io/controller-runtime/pkg/reconcile"
3230

3331
"k8s.io/apimachinery/pkg/runtime"
@@ -102,34 +100,26 @@ func (r *AccountReconciler) Reconcile(ctx context.Context, req ctrl.Request) (re
102100
func (r *AccountReconciler) reconcileAccount(ctx context.Context, account *cloudflareoperatoriov1.Account) ctrl.Result {
103101
secret := &corev1.Secret{}
104102
if err := r.Get(ctx, client.ObjectKey{Namespace: account.Spec.ApiToken.SecretRef.Namespace, Name: account.Spec.ApiToken.SecretRef.Name}, secret); err != nil {
105-
r.markFailed(account, err)
103+
common.MarkFalse(account, err)
106104
return ctrl.Result{RequeueAfter: time.Second * 30}
107105
}
108106

109107
cfApiToken := string(secret.Data["apiToken"])
110108
if cfApiToken == "" {
111-
r.markFailed(account, errors.New("Secret has no key named \"apiToken\""))
109+
common.MarkFalse(account, errors.New("Secret has no key named \"apiToken\""))
112110
return ctrl.Result{RequeueAfter: time.Second * 30}
113111
}
114112

115113
if r.Cf.APIToken != cfApiToken {
116114
cf, err := cloudflare.NewWithAPIToken(cfApiToken)
117115
if err != nil {
118-
r.markFailed(account, err)
116+
common.MarkFalse(account, err)
119117
return ctrl.Result{RequeueAfter: time.Second * 30}
120118
}
121119
*r.Cf = *cf
122120
}
123121

124-
apimeta.SetStatusCondition(&account.Status.Conditions, metav1.Condition{
125-
Type: "Ready",
126-
Status: metav1.ConditionTrue,
127-
Reason: "Ready",
128-
Message: "Account is ready",
129-
ObservedGeneration: account.Generation,
130-
})
131-
132-
metrics.AccountFailureCounter.WithLabelValues(account.Name).Set(0)
122+
common.MarkTrue(account, "Account is ready")
133123

134124
return ctrl.Result{RequeueAfter: account.Spec.Interval.Duration}
135125
}
@@ -139,15 +129,3 @@ func (r *AccountReconciler) reconcileDelete(account *cloudflareoperatoriov1.Acco
139129
metrics.AccountFailureCounter.DeleteLabelValues(account.Name)
140130
controllerutil.RemoveFinalizer(account, common.CloudflareOperatorFinalizer)
141131
}
142-
143-
// markFailed marks the account as failed
144-
func (r *AccountReconciler) markFailed(account *cloudflareoperatoriov1.Account, err error) {
145-
metrics.AccountFailureCounter.WithLabelValues(account.Name).Set(1)
146-
apimeta.SetStatusCondition(&account.Status.Conditions, metav1.Condition{
147-
Type: "Ready",
148-
Status: metav1.ConditionFalse,
149-
Reason: "Failed",
150-
Message: err.Error(),
151-
ObservedGeneration: account.Generation,
152-
})
153-
}

internal/controller/dnsrecord_controller.go

Lines changed: 12 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,11 @@ import (
2424
"reflect"
2525
"time"
2626

27+
"github.com/fluxcd/pkg/runtime/conditions"
2728
"github.com/fluxcd/pkg/runtime/patch"
2829
"golang.org/x/net/publicsuffix"
2930
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
3031
apierrors "k8s.io/apimachinery/pkg/api/errors"
31-
apimeta "k8s.io/apimachinery/pkg/api/meta"
32-
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3332
"k8s.io/apimachinery/pkg/runtime"
3433
apierrutil "k8s.io/apimachinery/pkg/util/errors"
3534
ctrl "sigs.k8s.io/controller-runtime"
@@ -124,7 +123,7 @@ func (r *DNSRecordReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
124123
}
125124

126125
if len(zones.Items) == 0 {
127-
r.markFailed(dnsrecord, fmt.Errorf("Zone %q not found", zoneName))
126+
common.MarkFalse(dnsrecord, fmt.Errorf("Zone %q not found", zoneName))
128127
return ctrl.Result{}, nil
129128
}
130129

@@ -149,24 +148,12 @@ func (r *DNSRecordReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
149148
// reconcileDNSRecord reconciles the dnsrecord
150149
func (r *DNSRecordReconciler) reconcileDNSRecord(ctx context.Context, dnsrecord *cloudflareoperatoriov1.DNSRecord, zone *cloudflareoperatoriov1.Zone) ctrl.Result {
151150
if r.Cf.APIToken == "" {
152-
apimeta.SetStatusCondition(&dnsrecord.Status.Conditions, metav1.Condition{
153-
Type: "Ready",
154-
Status: metav1.ConditionFalse,
155-
Reason: "NotReady",
156-
Message: "Cloudflare account is not ready",
157-
ObservedGeneration: dnsrecord.Generation,
158-
})
151+
common.MarkUnknown(dnsrecord, "Cloudflare account is not ready")
159152
return ctrl.Result{RequeueAfter: time.Second * 5}
160153
}
161154

162-
if condition := apimeta.FindStatusCondition(zone.Status.Conditions, "Ready"); condition == nil || condition.Status != metav1.ConditionTrue {
163-
apimeta.SetStatusCondition(&dnsrecord.Status.Conditions, metav1.Condition{
164-
Type: "Ready",
165-
Status: metav1.ConditionFalse,
166-
Reason: "NotReady",
167-
Message: "Zone is not ready",
168-
ObservedGeneration: dnsrecord.Generation,
169-
})
155+
if !conditions.IsTrue(zone, cloudflareoperatoriov1.ConditionTypeReady) {
156+
common.MarkUnknown(dnsrecord, "Zone is not ready")
170157
return ctrl.Result{RequeueAfter: time.Second * 5}
171158
}
172159

@@ -175,7 +162,7 @@ func (r *DNSRecordReconciler) reconcileDNSRecord(ctx context.Context, dnsrecord
175162
var err error
176163
existingRecord, err = r.Cf.GetDNSRecord(ctx, cloudflare.ZoneIdentifier(zone.Status.ID), dnsrecord.Status.RecordID)
177164
if err != nil {
178-
r.markFailed(dnsrecord, err)
165+
common.MarkFalse(dnsrecord, err)
179166
return ctrl.Result{}
180167
}
181168
} else {
@@ -185,7 +172,7 @@ func (r *DNSRecordReconciler) reconcileDNSRecord(ctx context.Context, dnsrecord
185172
Content: dnsrecord.Spec.Content,
186173
})
187174
if err != nil {
188-
r.markFailed(dnsrecord, err)
175+
common.MarkFalse(dnsrecord, err)
189176
return ctrl.Result{}
190177
}
191178
if len(cfExistingRecords) > 0 {
@@ -196,7 +183,7 @@ func (r *DNSRecordReconciler) reconcileDNSRecord(ctx context.Context, dnsrecord
196183
if (dnsrecord.Spec.Type == "A" || dnsrecord.Spec.Type == "AAAA") && dnsrecord.Spec.IPRef.Name != "" {
197184
ip := &cloudflareoperatoriov1.IP{}
198185
if err := r.Get(ctx, client.ObjectKey{Name: dnsrecord.Spec.IPRef.Name}, ip); err != nil {
199-
r.markFailed(dnsrecord, err)
186+
common.MarkFalse(dnsrecord, err)
200187
return ctrl.Result{RequeueAfter: time.Second * 30}
201188
}
202189
if ip.Spec.Address != dnsrecord.Spec.Content {
@@ -205,7 +192,7 @@ func (r *DNSRecordReconciler) reconcileDNSRecord(ctx context.Context, dnsrecord
205192
}
206193

207194
if *dnsrecord.Spec.Proxied && dnsrecord.Spec.TTL != 1 {
208-
r.markFailed(dnsrecord, errors.New("TTL must be 1 when proxied"))
195+
common.MarkFalse(dnsrecord, errors.New("TTL must be 1 when proxied"))
209196
return ctrl.Result{}
210197
}
211198

@@ -220,7 +207,7 @@ func (r *DNSRecordReconciler) reconcileDNSRecord(ctx context.Context, dnsrecord
220207
Data: dnsrecord.Spec.Data,
221208
})
222209
if err != nil {
223-
r.markFailed(dnsrecord, err)
210+
common.MarkFalse(dnsrecord, err)
224211
return ctrl.Result{RequeueAfter: time.Second * 30}
225212
}
226213
dnsrecord.Status.RecordID = newDNSRecord.ID
@@ -235,20 +222,12 @@ func (r *DNSRecordReconciler) reconcileDNSRecord(ctx context.Context, dnsrecord
235222
Priority: dnsrecord.Spec.Priority,
236223
Data: dnsrecord.Spec.Data,
237224
}); err != nil {
238-
r.markFailed(dnsrecord, err)
225+
common.MarkFalse(dnsrecord, err)
239226
return ctrl.Result{RequeueAfter: time.Second * 30}
240227
}
241228
}
242229

243-
apimeta.SetStatusCondition(&dnsrecord.Status.Conditions, metav1.Condition{
244-
Type: "Ready",
245-
Status: metav1.ConditionTrue,
246-
Reason: "Ready",
247-
Message: "DNS record synced",
248-
ObservedGeneration: dnsrecord.Generation,
249-
})
250-
251-
metrics.DnsRecordFailureCounter.WithLabelValues(dnsrecord.Namespace, dnsrecord.Name, dnsrecord.Spec.Name).Set(0)
230+
common.MarkTrue(dnsrecord, "DNS record synced")
252231

253232
return ctrl.Result{RequeueAfter: dnsrecord.Spec.Interval.Duration}
254233
}
@@ -264,18 +243,6 @@ func (r *DNSRecordReconciler) reconcileDelete(ctx context.Context, zoneID string
264243
return nil
265244
}
266245

267-
// markFailed marks the dnsrecord as failed
268-
func (r *DNSRecordReconciler) markFailed(dnsrecord *cloudflareoperatoriov1.DNSRecord, err error) {
269-
metrics.DnsRecordFailureCounter.WithLabelValues(dnsrecord.Namespace, dnsrecord.Name, dnsrecord.Spec.Name).Set(1)
270-
apimeta.SetStatusCondition(&dnsrecord.Status.Conditions, metav1.Condition{
271-
Type: "Ready",
272-
Status: metav1.ConditionFalse,
273-
Reason: "Failed",
274-
Message: err.Error(),
275-
ObservedGeneration: dnsrecord.Generation,
276-
})
277-
}
278-
279246
// comparePriority compares the priority nil safe
280247
func comparePriority(a, b *uint16) bool {
281248
if a == nil && b == nil {

0 commit comments

Comments
 (0)