@@ -63,6 +63,36 @@ func (r *DomainReconciler) Reconcile(ctx context.Context, req mcreconcile.Reques
6363 return ctrl.Result {}, client .IgnoreNotFound (err )
6464 }
6565
66+ logger .Info ("reconciling domain" )
67+ defer logger .Info ("reconcile complete" )
68+
69+ origStatus := domain .Status .DeepCopy ()
70+
71+ // Delegate all verification work (including timers/backoff)
72+ nextVerification := r .reconcileVerification (ctx , domain )
73+
74+ // Persist status if changed
75+ if ! equality .Semantic .DeepEqual (* origStatus , domain .Status ) {
76+ if err := cl .GetClient ().Status ().Update (ctx , domain ); err != nil {
77+ return ctrl.Result {}, fmt .Errorf ("failed updating domain status: %w" , err )
78+ }
79+ }
80+
81+ // If we have a future attempt scheduled, requeue for that time
82+ if ! nextVerification .IsZero () {
83+ if remaining := nextVerification .Sub (r .timeNow ()).Truncate (time .Second ); remaining > 0 {
84+ return ctrl.Result {RequeueAfter : remaining }, nil
85+ }
86+ }
87+
88+ return ctrl.Result {}, nil
89+ }
90+
91+ // reconcileVerification contains the verification logic.
92+ // It mutates domain.Status and returns the next verification attempt time (if any).
93+ func (r * DomainReconciler ) reconcileVerification (ctx context.Context , domain * networkingv1alpha.Domain ) time.Time {
94+ logger := log .FromContext (ctx )
95+
6696 domainStatus := domain .Status .DeepCopy ()
6797
6898 verifiedCondition := conditionutil .FindStatusConditionOrDefault (domainStatus .Conditions , & metav1.Condition {
@@ -92,8 +122,7 @@ func (r *DomainReconciler) Reconcile(ctx context.Context, req mcreconcile.Reques
92122 })
93123 verifiedHTTPCondition .ObservedGeneration = domain .Generation
94124
95- logger .Info ("reconciling domain" )
96- defer logger .Info ("reconcile complete" )
125+ var nextAttempt time.Time
97126
98127 if verifiedCondition .Status != metav1 .ConditionTrue {
99128 logger .Info ("domain ownership has not been verified." )
@@ -120,69 +149,62 @@ func (r *DomainReconciler) Reconcile(ctx context.Context, req mcreconcile.Reques
120149 verifiedDNSCondition .Message = "Update your DNS provider with record defined in `status.verification.dnsRecord`."
121150 verifiedHTTPCondition .Message = "Update your HTTP server with token defined in `status.verification.httpToken`."
122151 } else {
152+ // If we're not yet due, short-circuit
123153 if remaining := domainStatus .Verification .NextVerificationAttempt .Sub (r .timeNow ()).Truncate (time .Second ); remaining > 0 {
124154 logger .Info ("not attempting another validation until remaining time elapsed" , "remaining" , remaining )
125- // This exists because we want to communicate timing expectations to
126- // users, but by doing so we update the status information which triggers
127- // a reconcile, and without this check it could lead to many sequential
128- // updates to the resource.
129- //
130- // We can't have this logic in a predicate, because we couldn't schedule a
131- // future reconcile.
132- return ctrl.Result {RequeueAfter : remaining }, nil
133- }
134-
135- initialAttempt := verifiedCondition .LastTransitionTime .Time
136- elapsed := r .timeNow ().Sub (initialAttempt )
137- logger .Info ("time elapsed since last transition time" , "duration" , elapsed )
138-
139- requeueAfter := wait .Jitter (
140- r .Config .DomainVerification .GetRetryInterval (elapsed ),
141- r .Config .DomainVerification .RetryJitterMaxFactor ,
142- )
155+ nextAttempt = r .timeNow ().Add (remaining )
156+ } else {
157+ // Compute next backoff based on elapsed since last transition time
158+ initialAttempt := verifiedCondition .LastTransitionTime .Time
159+ elapsed := r .timeNow ().Sub (initialAttempt )
160+ logger .Info ("time elapsed since last transition time" , "duration" , elapsed )
161+
162+ requeueAfter := wait .Jitter (
163+ r .Config .DomainVerification .GetRetryInterval (elapsed ),
164+ r .Config .DomainVerification .RetryJitterMaxFactor ,
165+ )
143166
144- // Note that this will result in a reconcile to be enqueued immediately
145- // upon updating the status. The delayed requeue for attempting lookups
146- // is enforced above.
147- domainStatus .Verification .NextVerificationAttempt = metav1 .NewTime (r .timeNow ().Add (requeueAfter ))
167+ // Set the next attempt on status (this intentionally triggers an immediate reconcile via status update)
168+ domainStatus .Verification .NextVerificationAttempt = metav1 .NewTime (r .timeNow ().Add (requeueAfter ))
169+ nextAttempt = domainStatus .Verification .NextVerificationAttempt .Time
148170
149- ctx , cancel := context .WithTimeout (ctx , 5 * time .Second )
150- defer cancel ()
171+ // Perform verification attempts now
172+ attemptCtx , cancel := context .WithTimeout (ctx , 5 * time .Second )
173+ defer cancel ()
151174
152- r .attemptDNSVerification (ctx , domainStatus , verifiedDNSCondition )
175+ r .attemptDNSVerification (attemptCtx , domainStatus , verifiedDNSCondition )
153176
154- if verifiedDNSCondition .Status != metav1 .ConditionTrue {
155- r .attemptHTTPVerification (ctx , domainStatus , verifiedHTTPCondition )
156- }
177+ if verifiedDNSCondition .Status != metav1 .ConditionTrue {
178+ r .attemptHTTPVerification (attemptCtx , domainStatus , verifiedHTTPCondition )
179+ }
157180
158- if verifiedDNSCondition .Status == metav1 .ConditionTrue || verifiedHTTPCondition .Status == metav1 .ConditionTrue {
159- verifiedCondition .Status = metav1 .ConditionTrue
160- verifiedCondition .Reason = networkingv1alpha .DomainReasonVerified
161- verifiedCondition .Message = "Domain verification successful"
181+ if verifiedDNSCondition .Status == metav1 .ConditionTrue || verifiedHTTPCondition .Status == metav1 .ConditionTrue {
182+ verifiedCondition .Status = metav1 .ConditionTrue
183+ verifiedCondition .Reason = networkingv1alpha .DomainReasonVerified
184+ verifiedCondition .Message = "Domain verification successful"
162185
163- // Clear out verification info and unnecessary conditions
164- domainStatus .Verification = nil
165- apimeta .RemoveStatusCondition (& domainStatus .Conditions , networkingv1alpha .DomainConditionVerifiedDNS )
166- apimeta .RemoveStatusCondition (& domainStatus .Conditions , networkingv1alpha .DomainConditionVerifiedHTTP )
186+ // Clear verification scaffolding and sub-conditions
187+ domainStatus .Verification = nil
188+ apimeta .RemoveStatusCondition (& domainStatus .Conditions , networkingv1alpha .DomainConditionVerifiedDNS )
189+ apimeta .RemoveStatusCondition (& domainStatus .Conditions , networkingv1alpha .DomainConditionVerifiedHTTP )
190+ // When verified, no future verification timer is needed
191+ nextAttempt = time.Time {}
192+ }
167193 }
168194 }
169195 }
170196
197+ // Update conditions
171198 apimeta .SetStatusCondition (& domainStatus .Conditions , * verifiedCondition )
172-
173199 if verifiedCondition .Status == metav1 .ConditionFalse {
174200 apimeta .SetStatusCondition (& domainStatus .Conditions , * verifiedDNSCondition )
175201 apimeta .SetStatusCondition (& domainStatus .Conditions , * verifiedHTTPCondition )
176202 }
177203
178- if ! equality .Semantic .DeepEqual (domain .Status , domainStatus ) {
179- domain .Status = * domainStatus
180- if statusErr := cl .GetClient ().Status ().Update (ctx , domain ); statusErr != nil {
181- return ctrl.Result {}, fmt .Errorf ("failed updating domain status: %w" , statusErr )
182- }
183- }
204+ // Commit the staged status back
205+ domain .Status = * domainStatus
184206
185- return ctrl. Result {}, nil
207+ return nextAttempt
186208}
187209
188210func (r * DomainReconciler ) attemptDNSVerification (
@@ -271,7 +293,6 @@ func (r *DomainReconciler) attemptHTTPVerification(
271293 verifiedHTTPCondition .Reason = networkingv1alpha .DomainReasonVerificationUnexpectedResponse
272294 verifiedHTTPCondition .Message = fmt .Sprintf ("unexpected status code from HTTP token endpoint. HTTP %d" , httpResponse .StatusCode )
273295 }
274-
275296}
276297
277298// defaultHTTPGet is the default HTTP GET implementation for verification
0 commit comments