@@ -24,12 +24,14 @@ import (
24
24
"reflect"
25
25
"time"
26
26
27
- "github.com/go-logr/logr "
27
+ "github.com/fluxcd/pkg/runtime/patch "
28
28
"golang.org/x/net/publicsuffix"
29
29
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
30
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
30
31
apimeta "k8s.io/apimachinery/pkg/api/meta"
31
32
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32
33
"k8s.io/apimachinery/pkg/runtime"
34
+ apierrutil "k8s.io/apimachinery/pkg/util/errors"
33
35
ctrl "sigs.k8s.io/controller-runtime"
34
36
"sigs.k8s.io/controller-runtime/pkg/client"
35
37
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
@@ -58,6 +60,23 @@ func (r *DNSRecordReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Man
58
60
}); err != nil {
59
61
return err
60
62
}
63
+ if err := mgr .GetFieldIndexer ().IndexField (ctx , & cloudflareoperatoriov1.DNSRecord {}, cloudflareoperatoriov1 .OwnerRefUIDIndexKey ,
64
+ func (o client.Object ) []string {
65
+ obj := o .(* cloudflareoperatoriov1.DNSRecord )
66
+ ownerReferences := obj .GetOwnerReferences ()
67
+ var ownerReferencesUID string
68
+ for _ , ownerReference := range ownerReferences {
69
+ if ownerReference .Kind != "Ingress" {
70
+ continue
71
+ }
72
+ ownerReferencesUID = string (ownerReference .UID )
73
+ }
74
+
75
+ return []string {ownerReferencesUID }
76
+ },
77
+ ); err != nil {
78
+ return err
79
+ }
61
80
62
81
return ctrl .NewControllerManagedBy (mgr ).
63
82
For (& cloudflareoperatoriov1.DNSRecord {}).
@@ -71,110 +90,93 @@ func (r *DNSRecordReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Man
71
90
72
91
// Reconcile is part of the main kubernetes reconciliation loop which aims to
73
92
// move the current state of the cluster closer to the desired state.
74
- func (r * DNSRecordReconciler ) Reconcile (ctx context.Context , req ctrl.Request ) (ctrl.Result , error ) {
93
+ func (r * DNSRecordReconciler ) Reconcile (ctx context.Context , req ctrl.Request ) (result ctrl.Result , retErr error ) {
75
94
log := ctrl .LoggerFrom (ctx )
76
95
77
96
dnsrecord := & cloudflareoperatoriov1.DNSRecord {}
78
97
if err := r .Get (ctx , req .NamespacedName , dnsrecord ); err != nil {
79
98
return ctrl.Result {}, client .IgnoreNotFound (err )
80
99
}
81
100
82
- if r .Cf .APIToken == "" {
83
- apimeta .SetStatusCondition (& dnsrecord .Status .Conditions , metav1.Condition {
84
- Type : "Ready" ,
85
- Status : "False" ,
86
- Reason : "NotReady" ,
87
- Message : "Cloudflare account is not yet ready" ,
88
- ObservedGeneration : dnsrecord .Generation ,
89
- })
90
- if err := r .Status ().Update (ctx , dnsrecord ); err != nil {
91
- log .Error (err , "Failed to update DNSRecord status" )
92
- return ctrl.Result {}, err
101
+ patchHelper := patch .NewSerialPatcher (dnsrecord , r .Client )
102
+
103
+ defer func () {
104
+ patchOpts := []patch.Option {}
105
+
106
+ if errors .Is (retErr , reconcile .TerminalError (nil )) || (retErr == nil && (result .IsZero () || ! result .Requeue )) {
107
+ patchOpts = append (patchOpts , patch.WithStatusObservedGeneration {})
93
108
}
94
- return ctrl.Result {RequeueAfter : time .Second * 5 }, nil
95
- }
109
+
110
+ if err := patchHelper .Patch (ctx , dnsrecord , patchOpts ... ); err != nil {
111
+ if ! dnsrecord .DeletionTimestamp .IsZero () {
112
+ err = apierrutil .FilterOut (err , func (e error ) bool { return apierrors .IsNotFound (e ) })
113
+ }
114
+ retErr = apierrutil .Reduce (apierrutil .NewAggregate ([]error {retErr , err }))
115
+ }
116
+ }()
96
117
97
118
zoneName , _ := publicsuffix .EffectiveTLDPlusOne (dnsrecord .Spec .Name )
98
119
99
120
zones := & cloudflareoperatoriov1.ZoneList {}
100
- if err := r .List (ctx , zones ); err != nil {
101
- if err := r .markFailed (ctx , dnsrecord , err ); err != nil {
102
- log .Error (err , "Failed to update DNSRecord status" )
103
- return ctrl.Result {}, err
104
- }
121
+ if err := r .List (ctx , zones , client.MatchingFields {cloudflareoperatoriov1 .ZoneNameIndexKey : zoneName }); err != nil {
122
+ log .Error (err , "Failed to list zones" )
105
123
return ctrl.Result {RequeueAfter : time .Second * 30 }, nil
106
124
}
107
125
108
- zone := cloudflareoperatoriov1.Zone {}
109
- for _ , z := range zones .Items {
110
- if z .Spec .Name == zoneName {
111
- zone = z
112
- break
113
- }
126
+ if len (zones .Items ) == 0 {
127
+ r .markFailed (dnsrecord , fmt .Errorf ("Zone %q not found" , zoneName ))
128
+ return ctrl.Result {}, nil
114
129
}
115
130
116
- if zone .Spec .Name == "" {
117
- if err := r .markFailed (ctx , dnsrecord , fmt .Errorf ("Zone %q not found" , zoneName )); err != nil {
118
- log .Error (err , "Failed to update DNSRecord status" )
131
+ zone := & zones .Items [0 ]
132
+
133
+ if ! dnsrecord .DeletionTimestamp .IsZero () {
134
+ if err := r .reconcileDelete (ctx , zone .Status .ID , dnsrecord ); err != nil {
135
+ log .Error (err , "Failed to delete DNS record in Cloudflare, record may still exist in Cloudflare" )
119
136
return ctrl.Result {}, err
120
137
}
121
138
return ctrl.Result {}, nil
122
139
}
123
140
124
- if condition := apimeta .FindStatusCondition (zone .Status .Conditions , "Ready" ); condition == nil || condition .Status != "True" {
141
+ if ! controllerutil .ContainsFinalizer (dnsrecord , common .CloudflareOperatorFinalizer ) {
142
+ controllerutil .AddFinalizer (dnsrecord , common .CloudflareOperatorFinalizer )
143
+ return ctrl.Result {Requeue : true }, nil
144
+ }
145
+
146
+ return r .reconcileDNSRecord (ctx , dnsrecord , zone ), nil
147
+ }
148
+
149
+ // reconcileDNSRecord reconciles the dnsrecord
150
+ func (r * DNSRecordReconciler ) reconcileDNSRecord (ctx context.Context , dnsrecord * cloudflareoperatoriov1.DNSRecord , zone * cloudflareoperatoriov1.Zone ) ctrl.Result {
151
+ if r .Cf .APIToken == "" {
125
152
apimeta .SetStatusCondition (& dnsrecord .Status .Conditions , metav1.Condition {
126
153
Type : "Ready" ,
127
154
Status : "False" ,
128
155
Reason : "NotReady" ,
129
- Message : "Zone is not yet ready" ,
156
+ Message : "Cloudflare account is not yet ready" ,
130
157
ObservedGeneration : dnsrecord .Generation ,
131
158
})
132
- if err := r .Status ().Update (ctx , dnsrecord ); err != nil {
133
- log .Error (err , "Failed to update DNSRecord status" )
134
- return ctrl.Result {}, err
135
- }
136
- return ctrl.Result {RequeueAfter : time .Second * 5 }, nil
159
+ return ctrl.Result {RequeueAfter : time .Second * 5 }
137
160
}
138
161
139
- if ! controllerutil .ContainsFinalizer (dnsrecord , common .CloudflareOperatorFinalizer ) {
140
- controllerutil .AddFinalizer (dnsrecord , common .CloudflareOperatorFinalizer )
141
- if err := r .Update (ctx , dnsrecord ); err != nil {
142
- log .Error (err , "Failed to update DNSRecord finalizer" )
143
- return ctrl.Result {}, err
144
- }
145
- }
146
-
147
- if ! dnsrecord .DeletionTimestamp .IsZero () {
148
- if controllerutil .ContainsFinalizer (dnsrecord , common .CloudflareOperatorFinalizer ) {
149
- if err := r .finalizeDNSRecord (ctx , zone .Status .ID , log , dnsrecord ); err != nil && err .Error () != "Record does not exist. (81044)" {
150
- if err := r .markFailed (ctx , dnsrecord , err ); err != nil {
151
- log .Error (err , "Failed to update DNSRecord status" )
152
- return ctrl.Result {}, err
153
- }
154
- return ctrl.Result {RequeueAfter : time .Second * 30 }, nil
155
- }
156
- metrics .DnsRecordFailureCounter .DeleteLabelValues (dnsrecord .Namespace , dnsrecord .Name , dnsrecord .Spec .Name )
157
- }
158
-
159
- controllerutil .RemoveFinalizer (dnsrecord , common .CloudflareOperatorFinalizer )
160
- if err := r .Update (ctx , dnsrecord ); err != nil {
161
- return ctrl.Result {}, err
162
- }
163
- return ctrl.Result {}, nil
162
+ if condition := apimeta .FindStatusCondition (zone .Status .Conditions , "Ready" ); condition == nil || condition .Status != "True" {
163
+ apimeta .SetStatusCondition (& dnsrecord .Status .Conditions , metav1.Condition {
164
+ Type : "Ready" ,
165
+ Status : "False" ,
166
+ Reason : "NotReady" ,
167
+ Message : "Zone is not yet ready" ,
168
+ ObservedGeneration : dnsrecord .Generation ,
169
+ })
170
+ return ctrl.Result {RequeueAfter : time .Second * 5 }
164
171
}
165
172
166
- metrics .DnsRecordFailureCounter .WithLabelValues (dnsrecord .Namespace , dnsrecord .Name , dnsrecord .Spec .Name ).Set (0 )
167
-
168
173
var existingRecord cloudflare.DNSRecord
169
174
if dnsrecord .Status .RecordID != "" {
170
175
var err error
171
176
existingRecord , err = r .Cf .GetDNSRecord (ctx , cloudflare .ZoneIdentifier (zone .Status .ID ), dnsrecord .Status .RecordID )
172
177
if err != nil {
173
- if err := r .markFailed (ctx , dnsrecord , err ); err != nil {
174
- log .Error (err , "Failed to update DNSRecord status" )
175
- return ctrl.Result {}, err
176
- }
177
- return ctrl.Result {}, err
178
+ r .markFailed (dnsrecord , err )
179
+ return ctrl.Result {}
178
180
}
179
181
} else {
180
182
cfExistingRecords , _ , err := r .Cf .ListDNSRecords (ctx , cloudflare .ZoneIdentifier (zone .Status .ID ), cloudflare.ListDNSRecordsParams {
@@ -183,11 +185,8 @@ func (r *DNSRecordReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
183
185
Content : dnsrecord .Spec .Content ,
184
186
})
185
187
if err != nil {
186
- if err := r .markFailed (ctx , dnsrecord , err ); err != nil {
187
- log .Error (err , "Failed to update DNSRecord status" )
188
- return ctrl.Result {}, err
189
- }
190
- return ctrl.Result {}, err
188
+ r .markFailed (dnsrecord , err )
189
+ return ctrl.Result {}
191
190
}
192
191
if len (cfExistingRecords ) > 0 {
193
192
existingRecord = cfExistingRecords [0 ]
@@ -197,27 +196,17 @@ func (r *DNSRecordReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
197
196
if (dnsrecord .Spec .Type == "A" || dnsrecord .Spec .Type == "AAAA" ) && dnsrecord .Spec .IPRef .Name != "" {
198
197
ip := & cloudflareoperatoriov1.IP {}
199
198
if err := r .Get (ctx , client.ObjectKey {Name : dnsrecord .Spec .IPRef .Name }, ip ); err != nil {
200
- if err := r .markFailed (ctx , dnsrecord , err ); err != nil {
201
- log .Error (err , "Failed to update DNSRecord status" )
202
- return ctrl.Result {}, err
203
- }
204
- return ctrl.Result {RequeueAfter : time .Second * 30 }, nil
199
+ r .markFailed (dnsrecord , err )
200
+ return ctrl.Result {RequeueAfter : time .Second * 30 }
205
201
}
206
202
if ip .Spec .Address != dnsrecord .Spec .Content {
207
203
dnsrecord .Spec .Content = ip .Spec .Address
208
- if err := r .Update (ctx , dnsrecord ); err != nil {
209
- log .Error (err , "Failed to update DNSRecord" )
210
- return ctrl.Result {}, err
211
- }
212
204
}
213
205
}
214
206
215
207
if * dnsrecord .Spec .Proxied && dnsrecord .Spec .TTL != 1 {
216
- if err := r .markFailed (ctx , dnsrecord , errors .New ("TTL must be 1 when proxied" )); err != nil {
217
- log .Error (err , "Failed to update DNSRecord status" )
218
- return ctrl.Result {}, err
219
- }
220
- return ctrl.Result {}, nil
208
+ r .markFailed (dnsrecord , errors .New ("TTL must be 1 when proxied" ))
209
+ return ctrl.Result {}
221
210
}
222
211
223
212
if existingRecord .ID == "" {
@@ -231,30 +220,13 @@ func (r *DNSRecordReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
231
220
Data : dnsrecord .Spec .Data ,
232
221
})
233
222
if err != nil {
234
- if err := r .markFailed (ctx , dnsrecord , err ); err != nil {
235
- log .Error (err , "Failed to update DNSRecord status" )
236
- return ctrl.Result {}, err
237
- }
238
- return ctrl.Result {RequeueAfter : time .Second * 30 }, nil
223
+ r .markFailed (dnsrecord , err )
224
+ return ctrl.Result {RequeueAfter : time .Second * 30 }
239
225
}
240
- apimeta .SetStatusCondition (& dnsrecord .Status .Conditions , metav1.Condition {
241
- Type : "Ready" ,
242
- Status : "True" ,
243
- Reason : "Ready" ,
244
- Message : "DNS record synced" ,
245
- ObservedGeneration : dnsrecord .Generation ,
246
- })
247
226
dnsrecord .Status .RecordID = newDNSRecord .ID
248
- if err := r .Status ().Update (ctx , dnsrecord ); err != nil {
249
- log .Error (err , "Failed to update DNSRecord status" )
250
- return ctrl.Result {}, err
251
- }
252
- return ctrl.Result {RequeueAfter : dnsrecord .Spec .Interval .Duration }, nil
253
- }
254
-
255
- if ! compareDNSRecord (dnsrecord .Spec , existingRecord ) {
227
+ } else if ! compareDNSRecord (dnsrecord .Spec , existingRecord ) {
256
228
if _ , err := r .Cf .UpdateDNSRecord (ctx , cloudflare .ZoneIdentifier (zone .Status .ID ), cloudflare.UpdateDNSRecordParams {
257
- ID : existingRecord . ID ,
229
+ ID : dnsrecord . Status . RecordID ,
258
230
Name : dnsrecord .Spec .Name ,
259
231
Type : dnsrecord .Spec .Type ,
260
232
Content : dnsrecord .Spec .Content ,
@@ -263,26 +235,9 @@ func (r *DNSRecordReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
263
235
Priority : dnsrecord .Spec .Priority ,
264
236
Data : dnsrecord .Spec .Data ,
265
237
}); err != nil {
266
- if err := r .markFailed (ctx , dnsrecord , err ); err != nil {
267
- log .Error (err , "Failed to update DNSRecord status" )
268
- return ctrl.Result {}, err
269
- }
270
- return ctrl.Result {RequeueAfter : time .Second * 30 }, nil
238
+ r .markFailed (dnsrecord , err )
239
+ return ctrl.Result {RequeueAfter : time .Second * 30 }
271
240
}
272
- apimeta .SetStatusCondition (& dnsrecord .Status .Conditions , metav1.Condition {
273
- Type : "Ready" ,
274
- Status : "True" ,
275
- Reason : "Ready" ,
276
- Message : "DNS record synced" ,
277
- ObservedGeneration : dnsrecord .Generation ,
278
- })
279
- dnsrecord .Status .RecordID = existingRecord .ID
280
- if err := r .Status ().Update (ctx , dnsrecord ); err != nil {
281
- log .Error (err , "Failed to update DNSRecord status" )
282
- return ctrl.Result {}, err
283
- }
284
- log .Info ("DNS record updated in cloudflare" , "name" , existingRecord .Name , "id" , existingRecord .ID )
285
- return ctrl.Result {RequeueAfter : dnsrecord .Spec .Interval .Duration }, nil
286
241
}
287
242
288
243
apimeta .SetStatusCondition (& dnsrecord .Status .Conditions , metav1.Condition {
@@ -292,27 +247,25 @@ func (r *DNSRecordReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
292
247
Message : "DNS record synced" ,
293
248
ObservedGeneration : dnsrecord .Generation ,
294
249
})
295
- dnsrecord .Status .RecordID = existingRecord .ID
296
- if err := r .Status ().Update (ctx , dnsrecord ); err != nil {
297
- log .Error (err , "Failed to update DNSRecord status" )
298
- return ctrl.Result {}, err
299
- }
300
250
301
- return ctrl.Result {RequeueAfter : dnsrecord .Spec .Interval .Duration }, nil
251
+ metrics .DnsRecordFailureCounter .WithLabelValues (dnsrecord .Namespace , dnsrecord .Name , dnsrecord .Spec .Name ).Set (0 )
252
+
253
+ return ctrl.Result {RequeueAfter : dnsrecord .Spec .Interval .Duration }
302
254
}
303
255
304
- // finalizeDNSRecord deletes the DNS record from cloudflare
305
- func (r * DNSRecordReconciler ) finalizeDNSRecord (ctx context.Context , dnsRecordZoneId string , log logr.Logger , d * cloudflareoperatoriov1.DNSRecord ) error {
306
- if err := r .Cf .DeleteDNSRecord (ctx , cloudflare .ZoneIdentifier (dnsRecordZoneId ), d .Status .RecordID ); err != nil {
307
- log .Error (err , "Failed to delete DNS record in Cloudflare. Record may still exist in Cloudflare" )
256
+ // reconcileDelete reconciles the deletion of the dnsrecord
257
+ func (r * DNSRecordReconciler ) reconcileDelete (ctx context.Context , zoneID string , dnsrecord * cloudflareoperatoriov1.DNSRecord ) error {
258
+ if err := r .Cf .DeleteDNSRecord (ctx , cloudflare .ZoneIdentifier (zoneID ), dnsrecord .Status .RecordID ); err != nil && err .Error () != "Record does not exist. (81044)" {
308
259
return err
309
260
}
261
+ metrics .DnsRecordFailureCounter .DeleteLabelValues (dnsrecord .Namespace , dnsrecord .Name , dnsrecord .Spec .Name )
262
+ controllerutil .RemoveFinalizer (dnsrecord , common .CloudflareOperatorFinalizer )
310
263
311
264
return nil
312
265
}
313
266
314
267
// markFailed marks the dnsrecord as failed
315
- func (r * DNSRecordReconciler ) markFailed (ctx context. Context , dnsrecord * cloudflareoperatoriov1.DNSRecord , err error ) error {
268
+ func (r * DNSRecordReconciler ) markFailed (dnsrecord * cloudflareoperatoriov1.DNSRecord , err error ) {
316
269
metrics .DnsRecordFailureCounter .WithLabelValues (dnsrecord .Namespace , dnsrecord .Name , dnsrecord .Spec .Name ).Set (1 )
317
270
apimeta .SetStatusCondition (& dnsrecord .Status .Conditions , metav1.Condition {
318
271
Type : "Ready" ,
@@ -321,8 +274,6 @@ func (r *DNSRecordReconciler) markFailed(ctx context.Context, dnsrecord *cloudfl
321
274
Message : err .Error (),
322
275
ObservedGeneration : dnsrecord .Generation ,
323
276
})
324
-
325
- return r .Status ().Update (ctx , dnsrecord )
326
277
}
327
278
328
279
// comparePriority compares the priority nil safe
@@ -384,11 +335,11 @@ func compareDNSRecord(dnsRecordSpec cloudflareoperatoriov1.DNSRecordSpec, existi
384
335
return isEqual
385
336
}
386
337
387
- // requestsForIPChange returns a list of reconcile.Requests for DNSRecords that need to be reconciled
338
+ // requestsForIPChange returns a list of reconcile.Requests for DNSRecords that need to be reconciled if the IP changes
388
339
func (r * DNSRecordReconciler ) requestsForIPChange (ctx context.Context , o client.Object ) []reconcile.Request {
389
340
ip , ok := o .(* cloudflareoperatoriov1.IP )
390
341
if ! ok {
391
- err := fmt .Errorf ("expected a DNSRecord , got %T" , o )
342
+ err := fmt .Errorf ("expected an IP , got %T" , o )
392
343
ctrl .LoggerFrom (ctx ).Error (err , "failed to get requests for IP change" )
393
344
return nil
394
345
}
0 commit comments