...

Source file src/github.com/cert-manager/issuer-lib/controllers/issuer_controller.go

Documentation: github.com/cert-manager/issuer-lib/controllers

     1  /*
     2  Copyright 2023 The cert-manager Authors.
     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 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  // IssuerReconciler reconciles a TestIssuer object
    56  type IssuerReconciler struct {
    57  	ForObject v1alpha1.Issuer
    58  
    59  	FieldOwner  string
    60  	EventSource kubeutil.EventSource
    61  
    62  	// Client is a controller-runtime client used to get and set K8S API resources
    63  	client.Client
    64  	// Check connects to a CA and checks if it is available
    65  	signer.Check
    66  	// IgnoreIssuer is an optional function that can prevent the issuer controllers from
    67  	// reconciling an issuer resource.
    68  	signer.IgnoreIssuer
    69  
    70  	// EventRecorder is used for creating Kubernetes events on resources.
    71  	EventRecorder record.EventRecorder
    72  
    73  	// Clock is used to mock condition transition times in tests.
    74  	Clock clock.PassiveClock
    75  
    76  	// PreSetupWithManager is an optional function that can be used to perform
    77  	// additional setup before the controller is built and registered with the
    78  	// manager.
    79  	PreSetupWithManager func(context.Context, schema.GroupVersionKind, ctrl.Manager, *builder.Builder) error
    80  
    81  	// PostSetupWithManager is an optional function that can be used to perform
    82  	// additional setup after the controller is built and registered with the
    83  	// manager.
    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  	// The error returned by `reconcileStatusPatch` is meant for controller-runtime,
    93  	// not for us. That's why we aren't checking `reconcileError != nil` .
    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  // reconcileStatusPatch is responsible for reconciling the issuer. It will return the
   121  // result and reconcileError to be returned by the Reconcile function. It also returns
   122  // an issuerStatusPatch that the Reconcile function will apply to the issuer's status.
   123  // This function is split out from the Reconcile function to allow for easier testing.
   124  //
   125  // The error returned by `reconcileStatusPatch` is meant for controller-runtime,
   126  // not for the caller. The caller must not check the error (i.e., they must not
   127  // do `if err != nil...`).
   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) { // nolint:unparam
   133  	// Get the ClusterIssuer
   134  	issuer := r.ForObject.DeepCopyObject().(v1alpha1.Issuer)
   135  	forObjectGvk := r.ForObject.GetObjectKind().GroupVersionKind()
   136  	// calling IsInvalidated early to make sure the map is always cleared
   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 // done
   142  	} else if err != nil {
   143  		return result, nil, fmt.Errorf("unexpected get error: %v", err) // requeue with backoff
   144  	}
   145  
   146  	readyCondition := conditions.GetIssuerStatusCondition(issuer.GetStatus().Conditions, cmapi.IssuerConditionReady)
   147  
   148  	// Ignore Issuer if it is already permanently Failed
   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 // done
   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) // requeue with backoff
   162  		}
   163  		if ignore {
   164  			logger.V(1).Info("IgnoreIssuer() returned true. Ignoring.")
   165  			return result, nil, nil // done
   166  		}
   167  	}
   168  
   169  	// We now have a Issuer that belongs to us so we are responsible
   170  	// for updating its Status.
   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  	// Add a Ready condition if one does not already exist. Set initial Status
   189  	// to Unknown.
   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  		// To continue reconciling this Issuer, we must re-run the reconcile loop
   198  		// after adding the Unknown Ready condition. This update will trigger a
   199  		// new reconcile loop, so we don't need to requeue here.
   200  		return result, issuerStatusPatch, nil // apply patch, done
   201  	}
   202  
   203  	var err error
   204  	if (readyCondition.Status == cmmeta.ConditionTrue) && (reportedError != nil) {
   205  		// We received an error from a Certificaterequest while our current status is Ready,
   206  		// update the ready state of the issuer to reflect the error.
   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 // apply patch, done
   221  	}
   222  
   223  	isPermanentError := errors.As(err, &signer.PermanentError{})
   224  	if isPermanentError {
   225  		// fail permanently
   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) // apply patch, done
   234  	} else {
   235  		// retry
   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 // apply patch, requeue with backoff
   244  	}
   245  }
   246  
   247  // SetupWithManager sets up the controller with the Manager.
   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  			// we are only interested in changes to the .Spec part of the issuer
   258  			// this also prevents us to get in fast reconcile loop when setting the
   259  			// status to Pending causing the resource to update, while we only want
   260  			// to re-reconcile with backoff/ when a resource becomes available.
   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 // free setup function
   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 // free setup function
   281  		return err
   282  	}
   283  	return nil
   284  }
   285  

View as plain text