1
16
17 package controllers
18
19 import (
20 "context"
21 "errors"
22 "fmt"
23
24 cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
25 cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1"
26 "github.com/go-logr/logr"
27 corev1 "k8s.io/api/core/v1"
28 apierrors "k8s.io/apimachinery/pkg/api/errors"
29 "k8s.io/apimachinery/pkg/runtime/schema"
30 utilerrors "k8s.io/apimachinery/pkg/util/errors"
31 "k8s.io/client-go/tools/record"
32 "k8s.io/utils/clock"
33 "k8s.io/utils/ptr"
34 ctrl "sigs.k8s.io/controller-runtime"
35 "sigs.k8s.io/controller-runtime/pkg/builder"
36 "sigs.k8s.io/controller-runtime/pkg/client"
37 "sigs.k8s.io/controller-runtime/pkg/controller"
38 "sigs.k8s.io/controller-runtime/pkg/log"
39 "sigs.k8s.io/controller-runtime/pkg/predicate"
40 "sigs.k8s.io/controller-runtime/pkg/reconcile"
41
42 v1alpha1 "github.com/cert-manager/issuer-lib/api/v1alpha1"
43 "github.com/cert-manager/issuer-lib/conditions"
44 "github.com/cert-manager/issuer-lib/controllers/signer"
45 "github.com/cert-manager/issuer-lib/internal/kubeutil"
46 "github.com/cert-manager/issuer-lib/internal/ssaclient"
47 )
48
49 const (
50 eventIssuerChecked = "Checked"
51 eventIssuerRetryableError = "RetryableError"
52 eventIssuerPermanentError = "PermanentError"
53 )
54
55
56 type IssuerReconciler struct {
57 ForObject v1alpha1.Issuer
58
59 FieldOwner string
60 EventSource kubeutil.EventSource
61
62
63 client.Client
64
65 signer.Check
66
67
68 signer.IgnoreIssuer
69
70
71 EventRecorder record.EventRecorder
72
73
74 Clock clock.PassiveClock
75
76
77
78
79 PreSetupWithManager func(context.Context, schema.GroupVersionKind, ctrl.Manager, *builder.Builder) error
80
81
82
83
84 PostSetupWithManager func(context.Context, schema.GroupVersionKind, ctrl.Manager, controller.Controller) error
85 }
86
87 func (r *IssuerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, returnedError error) {
88 logger := log.FromContext(ctx).WithName("Reconcile")
89
90 logger.V(2).Info("Starting reconcile loop", "name", req.Name, "namespace", req.Namespace)
91
92
93
94 result, issuerStatusPatch, reconcileError := r.reconcileStatusPatch(logger, ctx, req)
95
96 logger.V(2).Info("Got StatusPatch result", "result", result, "patch", issuerStatusPatch, "error", reconcileError)
97 if issuerStatusPatch != nil {
98 cr, patch, err := ssaclient.GenerateIssuerStatusPatch(r.ForObject, req.Name, req.Namespace, issuerStatusPatch)
99 if err != nil {
100 return ctrl.Result{}, utilerrors.NewAggregate([]error{err, reconcileError})
101 }
102
103 if err := r.Client.Status().Patch(ctx, cr, patch, &client.SubResourcePatchOptions{
104 PatchOptions: client.PatchOptions{
105 FieldManager: r.FieldOwner,
106 Force: ptr.To(true),
107 },
108 }); err != nil {
109 if !apierrors.IsNotFound(err) {
110 return ctrl.Result{}, utilerrors.NewAggregate([]error{err, reconcileError})
111 }
112
113 logger.V(1).Info("Not found. Ignoring.")
114 }
115 }
116
117 return result, reconcileError
118 }
119
120
121
122
123
124
125
126
127
128 func (r *IssuerReconciler) reconcileStatusPatch(
129 logger logr.Logger,
130 ctx context.Context,
131 req ctrl.Request,
132 ) (result ctrl.Result, issuerStatusPatch *v1alpha1.IssuerStatus, reconcileError error) {
133
134 issuer := r.ForObject.DeepCopyObject().(v1alpha1.Issuer)
135 forObjectGvk := r.ForObject.GetObjectKind().GroupVersionKind()
136
137 reportedError := r.EventSource.HasReportedError(forObjectGvk, req.NamespacedName)
138
139 if err := r.Client.Get(ctx, req.NamespacedName, issuer); err != nil && apierrors.IsNotFound(err) {
140 logger.V(1).Info("Issuer not found. Ignoring.")
141 return result, nil, nil
142 } else if err != nil {
143 return result, nil, fmt.Errorf("unexpected get error: %v", err)
144 }
145
146 readyCondition := conditions.GetIssuerStatusCondition(issuer.GetStatus().Conditions, cmapi.IssuerConditionReady)
147
148
149 isFailed := (readyCondition != nil) &&
150 (readyCondition.Status == cmmeta.ConditionFalse) &&
151 (readyCondition.Reason == v1alpha1.IssuerConditionReasonFailed) &&
152 (readyCondition.ObservedGeneration >= issuer.GetGeneration())
153 if isFailed {
154 logger.V(1).Info("Issuer is Failed Permanently. Ignoring.")
155 return result, nil, nil
156 }
157
158 if r.IgnoreIssuer != nil {
159 ignore, err := r.IgnoreIssuer(ctx, issuer)
160 if err != nil {
161 return result, nil, fmt.Errorf("failed to check if issuer should be ignored: %v", err)
162 }
163 if ignore {
164 logger.V(1).Info("IgnoreIssuer() returned true. Ignoring.")
165 return result, nil, nil
166 }
167 }
168
169
170
171 issuerStatusPatch = &v1alpha1.IssuerStatus{}
172
173 setReadyCondition := func(
174 status cmmeta.ConditionStatus,
175 reason, message string,
176 ) string {
177 condition, _ := conditions.SetIssuerStatusCondition(
178 r.Clock,
179 issuer.GetStatus().Conditions,
180 &issuerStatusPatch.Conditions,
181 issuer.GetGeneration(),
182 cmapi.IssuerConditionReady,
183 status, reason, message,
184 )
185 return condition.Message
186 }
187
188
189
190 if readyCondition == nil {
191 logger.V(1).Info("Initializing Ready condition")
192 setReadyCondition(
193 cmmeta.ConditionUnknown,
194 v1alpha1.IssuerConditionReasonInitializing,
195 fmt.Sprintf("%s has started reconciling this Issuer", r.FieldOwner),
196 )
197
198
199
200 return result, issuerStatusPatch, nil
201 }
202
203 var err error
204 if (readyCondition.Status == cmmeta.ConditionTrue) && (reportedError != nil) {
205
206
207 err = reportedError
208 } else {
209 err = r.Check(log.IntoContext(ctx, logger), issuer)
210 }
211 if err == nil {
212 logger.V(1).Info("Successfully finished the reconciliation.")
213 message := setReadyCondition(
214 cmmeta.ConditionTrue,
215 v1alpha1.IssuerConditionReasonChecked,
216 "Succeeded checking the issuer",
217 )
218 r.EventRecorder.Event(issuer, corev1.EventTypeNormal, eventIssuerChecked, message)
219
220 return result, issuerStatusPatch, nil
221 }
222
223 isPermanentError := errors.As(err, &signer.PermanentError{})
224 if isPermanentError {
225
226 logger.V(1).Error(err, "Permanent Issuer error. Marking as failed.")
227 message := setReadyCondition(
228 cmmeta.ConditionFalse,
229 v1alpha1.IssuerConditionReasonFailed,
230 fmt.Sprintf("Failed permanently: %s", err),
231 )
232 r.EventRecorder.Event(issuer, corev1.EventTypeWarning, eventIssuerPermanentError, message)
233 return result, issuerStatusPatch, reconcile.TerminalError(err)
234 } else {
235
236 logger.V(1).Error(err, "Retryable Issuer error.")
237 message := setReadyCondition(
238 cmmeta.ConditionFalse,
239 v1alpha1.IssuerConditionReasonPending,
240 fmt.Sprintf("Not ready yet: %s", err),
241 )
242 r.EventRecorder.Event(issuer, corev1.EventTypeWarning, eventIssuerRetryableError, message)
243 return result, issuerStatusPatch, err
244 }
245 }
246
247
248 func (r *IssuerReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error {
249 if err := kubeutil.SetGroupVersionKind(mgr.GetScheme(), r.ForObject); err != nil {
250 return err
251 }
252 forObjectGvk := r.ForObject.GetObjectKind().GroupVersionKind()
253
254 build := ctrl.NewControllerManagedBy(mgr).
255 For(
256 r.ForObject,
257
258
259
260
261 builder.WithPredicates(
262 predicate.ResourceVersionChangedPredicate{},
263 IssuerPredicate{},
264 ),
265 ).
266 WatchesRawSource(r.EventSource.AddConsumer(forObjectGvk))
267
268 if r.PreSetupWithManager != nil {
269 err := r.PreSetupWithManager(ctx, forObjectGvk, mgr, build)
270 r.PreSetupWithManager = nil
271 if err != nil {
272 return err
273 }
274 }
275
276 if controller, err := build.Build(r); err != nil {
277 return err
278 } else if r.PostSetupWithManager != nil {
279 err := r.PostSetupWithManager(ctx, forObjectGvk, mgr, controller)
280 r.PostSetupWithManager = nil
281 return err
282 }
283 return nil
284 }
285
View as plain text