1
16
17 package controllers
18
19 import (
20 "context"
21 "errors"
22 "fmt"
23 "time"
24
25 cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
26 cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1"
27 "github.com/go-logr/logr"
28 apierrors "k8s.io/apimachinery/pkg/api/errors"
29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30 "k8s.io/apimachinery/pkg/runtime"
31 "k8s.io/apimachinery/pkg/runtime/schema"
32 utilerrors "k8s.io/apimachinery/pkg/util/errors"
33 "k8s.io/client-go/tools/record"
34 "k8s.io/utils/clock"
35 "k8s.io/utils/ptr"
36 ctrl "sigs.k8s.io/controller-runtime"
37 "sigs.k8s.io/controller-runtime/pkg/builder"
38 "sigs.k8s.io/controller-runtime/pkg/client"
39 "sigs.k8s.io/controller-runtime/pkg/controller"
40 "sigs.k8s.io/controller-runtime/pkg/log"
41 "sigs.k8s.io/controller-runtime/pkg/predicate"
42 "sigs.k8s.io/controller-runtime/pkg/reconcile"
43
44 v1alpha1 "github.com/cert-manager/issuer-lib/api/v1alpha1"
45 "github.com/cert-manager/issuer-lib/conditions"
46 "github.com/cert-manager/issuer-lib/controllers/signer"
47 "github.com/cert-manager/issuer-lib/internal/kubeutil"
48 )
49
50
51
52
53
54
55 type RequestController struct {
56 IssuerTypes []v1alpha1.Issuer
57 ClusterIssuerTypes []v1alpha1.Issuer
58
59 FieldOwner string
60 MaxRetryDuration time.Duration
61 EventSource kubeutil.EventSource
62
63
64 client.Client
65
66 signer.Sign
67
68
69 signer.IgnoreCertificateRequest
70
71
72 EventRecorder record.EventRecorder
73
74
75 Clock clock.PassiveClock
76
77
78
79
80 PreSetupWithManager func(context.Context, schema.GroupVersionKind, ctrl.Manager, *builder.Builder) error
81
82
83
84
85 PostSetupWithManager func(context.Context, schema.GroupVersionKind, ctrl.Manager, controller.Controller) error
86
87 allIssuerTypes []IssuerType
88
89 initialised bool
90 requestType client.Object
91 requestPredicate predicate.Predicate
92 matchIssuerType MatchIssuerType
93 requestObjectHelperCreator RequestObjectHelperCreator
94 }
95
96 type MatchIssuerType func(client.Object) (v1alpha1.Issuer, client.ObjectKey, error)
97 type RequestObjectHelperCreator func(client.Object) RequestObjectHelper
98
99 type IssuerType struct {
100 Type v1alpha1.Issuer
101 IsNamespaced bool
102 }
103
104 func (r *RequestController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
105 logger := log.FromContext(ctx).WithName("Reconcile")
106
107 logger.V(2).Info("Starting reconcile loop", "name", req.Name, "namespace", req.Namespace)
108
109
110
111 result, statusPatch, reconcileError := r.reconcileStatusPatch(logger, ctx, req)
112
113 if statusPatch != nil {
114 obj, patch, err := statusPatch.Patch()
115 if err != nil {
116 return ctrl.Result{}, utilerrors.NewAggregate([]error{err, reconcileError})
117 }
118
119 logger.V(2).Info("Got StatusPatch result", "result", result, "error", reconcileError, "patch", patch)
120
121 if err := r.Client.Status().Patch(ctx, obj, patch, &client.SubResourcePatchOptions{
122 PatchOptions: client.PatchOptions{
123 FieldManager: r.FieldOwner,
124 Force: ptr.To(true),
125 },
126 }); err != nil {
127 if !apierrors.IsNotFound(err) {
128 return ctrl.Result{}, utilerrors.NewAggregate([]error{err, reconcileError})
129 }
130
131 logger.V(1).Info("Request not found. Ignoring.")
132 }
133 } else {
134 logger.V(2).Info("Got nil StatusPatch result", "result", result, "error", reconcileError)
135 }
136
137 return result, reconcileError
138 }
139
140
141
142
143
144
145
146
147
148
149 func (r *RequestController) reconcileStatusPatch(
150 logger logr.Logger,
151 ctx context.Context,
152 req ctrl.Request,
153 ) (result ctrl.Result, _ RequestPatch, reconcileError error) {
154 requestObject := r.requestType.DeepCopyObject().(client.Object)
155
156 if err := r.Client.Get(ctx, req.NamespacedName, requestObject); err != nil && apierrors.IsNotFound(err) {
157 logger.V(1).Info("Request not found. Ignoring.")
158 return result, nil, nil
159 } else if err != nil {
160 return result, nil, fmt.Errorf("unexpected get error: %v", err)
161 }
162
163
164 issuerObject, issuerName, err := r.matchIssuerType(requestObject)
165
166 if err != nil {
167 logger.V(1).Info("Request has a foreign issuer. Ignoring.", "error", err)
168 return result, nil, nil
169 }
170 issuerGvk := issuerObject.GetObjectKind().GroupVersionKind()
171
172
173 requestObjectHelper := r.requestObjectHelperCreator(requestObject)
174
175
176
177 if !requestObjectHelper.IsApproved() && !requestObjectHelper.IsDenied() {
178 logger.V(1).Info("Request has not been approved or denied. Ignoring.")
179 return result, nil, nil
180 }
181
182
183 if requestObjectHelper.IsReady() {
184 logger.V(1).Info("Request is Ready. Ignoring.")
185 return result, nil, nil
186 }
187
188
189 if requestObjectHelper.IsFailed() {
190 logger.V(1).Info("Request is Failed. Ignoring.")
191 return result, nil, nil
192 }
193
194
195 if requestObjectHelper.IsDenied() {
196 logger.V(1).Info("Request is Denied. Ignoring.")
197 return result, nil, nil
198 }
199
200 if r.IgnoreCertificateRequest != nil {
201 ignore, err := r.IgnoreCertificateRequest(
202 ctx,
203 requestObjectHelper.RequestObject(),
204 issuerGvk,
205 issuerName,
206 )
207 if err != nil {
208 logger.V(1).Error(err, "Unexpected error while checking if Request should be ignored")
209 return result, nil, fmt.Errorf("failed to check if Request should be ignored: %v", err)
210 }
211
212 if ignore {
213 logger.V(1).Info("Ignoring Request")
214 return result, nil, nil
215 }
216 }
217
218
219
220 statusPatch := requestObjectHelper.NewPatch(
221 r.Clock,
222 r.FieldOwner,
223 r.EventRecorder,
224 )
225
226
227
228 if statusPatch.SetInitializing() {
229 logger.V(1).Info("Initialised Ready condition")
230
231
232
233
234 return result, statusPatch, nil
235 }
236
237 if err := r.Client.Get(ctx, issuerName, issuerObject); err != nil && apierrors.IsNotFound(err) {
238 logger.V(1).Info("Issuer not found. Waiting for it to be created")
239 statusPatch.SetWaitingForIssuerExist(err)
240
241 return result, statusPatch, nil
242 } else if err != nil {
243 logger.V(1).Error(err, "Unexpected error while getting Issuer")
244 statusPatch.SetUnexpectedError(err)
245
246 return result, nil, fmt.Errorf("unexpected get error: %v", err)
247 }
248
249 readyCondition := conditions.GetIssuerStatusCondition(
250 issuerObject.GetStatus().Conditions,
251 cmapi.IssuerConditionReady,
252 )
253 if readyCondition == nil {
254 logger.V(1).Info("Issuer is not Ready yet (no ready condition). Waiting for it to become ready.")
255 statusPatch.SetWaitingForIssuerReadyNoCondition()
256
257 return result, statusPatch, nil
258 }
259 if readyCondition.ObservedGeneration < issuerObject.GetGeneration() {
260 logger.V(1).Info("Issuer is not Ready yet (ready condition out-of-date). Waiting for it to become ready.", "issuer ready condition", readyCondition)
261 statusPatch.SetWaitingForIssuerReadyOutdated()
262
263 return result, statusPatch, nil
264 }
265 if readyCondition.Status != cmmeta.ConditionTrue {
266 logger.V(1).Info("Issuer is not Ready yet (status == false). Waiting for it to become ready.", "issuer ready condition", readyCondition)
267 statusPatch.SetWaitingForIssuerReadyNotReady(readyCondition)
268
269 return result, statusPatch, nil
270 }
271
272 signedCertificate, err := r.Sign(log.IntoContext(ctx, logger), requestObjectHelper.RequestObject(), issuerObject)
273 if err == nil {
274 logger.V(1).Info("Successfully finished the reconciliation.")
275 statusPatch.SetIssued(signedCertificate)
276
277 return result, statusPatch, nil
278 }
279
280
281
282 if issuerError := new(signer.IssuerError); errors.As(err, issuerError) {
283 if reportError := r.EventSource.ReportError(
284 issuerGvk, client.ObjectKeyFromObject(issuerObject),
285 issuerError.Err,
286 ); reportError != nil {
287 return result, nil, fmt.Errorf("unexpected ReportError error: %v", reportError)
288 }
289
290 logger.V(1).Info("Issuer is not Ready yet (ready condition out-of-date). Waiting for it to become ready.", "issuer-error", issuerError)
291 statusPatch.SetWaitingForIssuerReadyOutdated()
292
293 return result, statusPatch, nil
294 }
295
296 didCustomConditionTransition := false
297 if targetCustom := new(signer.SetCertificateRequestConditionError); errors.As(err, targetCustom) {
298 logger.V(1).Info("Set RequestCondition error. Setting condition.", "error", err)
299 didCustomConditionTransition = statusPatch.SetCustomCondition(
300 string(targetCustom.ConditionType),
301 metav1.ConditionStatus(targetCustom.Status),
302 targetCustom.Reason,
303 targetCustom.Error(),
304 )
305 }
306
307
308 isPending := errors.As(err, &signer.PendingError{})
309 isPermanentError := errors.As(err, &signer.PermanentError{})
310 pastMaxRetryDuration := r.Clock.Now().After(requestObject.GetCreationTimestamp().Add(r.MaxRetryDuration))
311 switch {
312 case isPending:
313
314
315
316
317
318
319 logger.V(1).WithValues("reason", err.Error()).Info("Signing in progress.")
320 statusPatch.SetPending(fmt.Sprintf("Signing still in progress. Reason: %s", err))
321
322
323
324 if didCustomConditionTransition {
325 return result, statusPatch, nil
326 } else {
327 result.Requeue = true
328 return result, statusPatch, nil
329 }
330 case isPermanentError:
331 logger.V(1).Error(err, "Permanent Request error. Marking as failed.")
332 statusPatch.SetPermanentError(err)
333 return result, statusPatch, reconcile.TerminalError(err)
334 case pastMaxRetryDuration:
335 logger.V(1).Error(err, "Request has been retried for too long. Marking as failed.")
336 statusPatch.SetPermanentError(err)
337 return result, statusPatch, reconcile.TerminalError(err)
338 default:
339
340 logger.V(1).Error(err, "Got an error, will be retried.")
341 statusPatch.SetRetryableError(err)
342
343
344
345 if didCustomConditionTransition {
346 return result, statusPatch, reconcile.TerminalError(err)
347 } else {
348 return result, statusPatch, err
349 }
350 }
351 }
352
353 func (r *RequestController) setAllIssuerTypesWithGroupVersionKind(scheme *runtime.Scheme) error {
354 issuers := make([]IssuerType, 0, len(r.IssuerTypes)+len(r.ClusterIssuerTypes))
355 for _, issuer := range r.IssuerTypes {
356 issuers = append(issuers, IssuerType{
357 Type: issuer,
358 IsNamespaced: true,
359 })
360
361 }
362 for _, issuer := range r.ClusterIssuerTypes {
363 issuers = append(issuers, IssuerType{
364 Type: issuer,
365 IsNamespaced: false,
366 })
367 }
368
369 for _, issuer := range issuers {
370 if err := kubeutil.SetGroupVersionKind(scheme, issuer.Type); err != nil {
371 return err
372 }
373 }
374
375 r.allIssuerTypes = issuers
376
377 return nil
378 }
379
380 func (r *RequestController) AllIssuerTypes() []IssuerType {
381 return r.allIssuerTypes
382 }
383
384 func (r *RequestController) Init(
385 requestType client.Object,
386 requestPredicate predicate.Predicate,
387 matchIssuerType MatchIssuerType,
388 requestObjectHelperCreator RequestObjectHelperCreator,
389 ) *RequestController {
390 r.requestType = requestType
391 r.requestPredicate = requestPredicate
392 r.matchIssuerType = matchIssuerType
393 r.requestObjectHelperCreator = requestObjectHelperCreator
394
395 r.initialised = true
396
397 return r
398 }
399
400
401 func (r *RequestController) SetupWithManager(
402 ctx context.Context,
403 mgr ctrl.Manager,
404 ) error {
405 if !r.initialised {
406 return fmt.Errorf("must call Init(...) before calling SetupWithManager(...)")
407 }
408
409 if err := kubeutil.SetGroupVersionKind(mgr.GetScheme(), r.requestType); err != nil {
410 return err
411 }
412
413 if err := r.setAllIssuerTypesWithGroupVersionKind(mgr.GetScheme()); err != nil {
414 return err
415 }
416
417 build := ctrl.
418 NewControllerManagedBy(mgr).
419 For(
420 r.requestType,
421
422
423
424
425 builder.WithPredicates(
426 predicate.ResourceVersionChangedPredicate{},
427 r.requestPredicate,
428 ),
429 )
430
431
432
433
434
435 for _, issuerType := range r.AllIssuerTypes() {
436 issuerType := issuerType
437 gvk := issuerType.Type.GetObjectKind().GroupVersionKind()
438
439
440
441
442
443
444
445
446
447 timeout := mgr.GetControllerOptions().CacheSyncTimeout
448 if timeout == 0 {
449 timeout = 2 * time.Minute
450 }
451 cacheSyncCtx, cancel := context.WithTimeout(ctx, timeout)
452 defer cancel()
453
454 resourceHandler, err := kubeutil.NewLinkedResourceHandler(
455 cacheSyncCtx,
456 mgr.GetLogger(),
457 mgr.GetScheme(),
458 mgr.GetCache(),
459 r.requestType,
460 func(rawObj client.Object) []string {
461 issuerObject, issuerName, err := r.matchIssuerType(rawObj)
462 if err != nil || issuerObject.GetObjectKind().GroupVersionKind() != gvk {
463 return nil
464 }
465
466 return []string{fmt.Sprintf("%s/%s", issuerName.Namespace, issuerName.Name)}
467 },
468 nil,
469 )
470 if err != nil {
471 return err
472 }
473
474 build = build.Watches(
475 issuerType.Type,
476 resourceHandler,
477 builder.WithPredicates(
478 predicate.ResourceVersionChangedPredicate{},
479 LinkedIssuerPredicate{},
480 ),
481 )
482 }
483
484 if r.PreSetupWithManager != nil {
485 err := r.PreSetupWithManager(ctx, r.requestType.GetObjectKind().GroupVersionKind(), mgr, build)
486 r.PreSetupWithManager = nil
487 if err != nil {
488 return err
489 }
490 }
491
492 if controller, err := build.Build(r); err != nil {
493 return err
494 } else if r.PostSetupWithManager != nil {
495 err := r.PostSetupWithManager(ctx, r.requestType.GetObjectKind().GroupVersionKind(), mgr, controller)
496 r.PostSetupWithManager = nil
497 return err
498 }
499 return nil
500 }
501
View as plain text