cert-manager/pkg/controller/cainjector/reconciler.go
Tim Ramlot bce882b477
use cainjector feature flags
Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com>
2023-05-03 19:52:13 +02:00

186 lines
6.0 KiB
Go

/*
Copyright 2020 The cert-manager Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cainjector
import (
"context"
"fmt"
"strings"
"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/utils/pointer"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/cert-manager/cert-manager/internal/cainjector/feature"
certmanager "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
logf "github.com/cert-manager/cert-manager/pkg/logs"
utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature"
)
// This file contains logic to create reconcilers. By default a
// reconciler is created for each of the injectables- CustomResourceDefinition,
// Validating/MutatingWebhookConfiguration, APIService and gets triggered for
// events on those resources as well as on Secrets and Certificates.
// reconciler syncs CA data from source to injectable.
type reconciler struct {
// newInjectableTarget knows how to create a new injectable targt for
// the injectable being reconciled.
newInjectableTarget NewInjectableTarget
// sources is a list of available 'data sources' that can be used to extract
// caBundles from various source.
// This is defined as a variable to allow an instance of the secret-based
// cainjector to run even when Certificate resources cannot we watched due to
// the conversion webhook not being available.
sources []caDataSource
log logr.Logger
client.Client
// if set, the reconciler is namespace scoped
namespace string
// fieldManager is the manager name used for the Apply operations.
fieldManager string
resourceName string // just used for logging
}
// Reconcile attempts to ensure that a particular injectable has all the CAs injected that
// it has requested.
func (r *reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
// fetch the target object
target := r.newInjectableTarget()
log := r.log.WithValues("kind", r.resourceName, "name", req.Name)
log.V(logf.DebugLevel).Info("Parsing injectable")
if err := r.Client.Get(ctx, req.NamespacedName, target.AsObject()); err != nil {
if dropNotFound(err) == nil {
// don't requeue on deletions, which yield a non-found object
log.V(logf.DebugLevel).Info("ignoring", "reason", "not found", "err", err)
return ctrl.Result{}, nil
}
log.Error(err, "unable to fetch target object to inject into")
return ctrl.Result{}, err
}
metaObj, err := meta.Accessor(target.AsObject())
if err != nil {
log.Error(err, "unable to get metadata for object")
return ctrl.Result{}, err
}
// ignore resources that are being deleted
if !metaObj.GetDeletionTimestamp().IsZero() {
log.V(logf.DebugLevel).Info("ignoring", "reason", "object has a non-zero deletion timestamp")
return ctrl.Result{}, nil
}
// ensure that it wants injection
dataSource, err := r.caDataSourceFor(log, metaObj)
if err != nil {
log.V(logf.DebugLevel).Info("failed to determine ca data source for injectable")
return ctrl.Result{}, nil
}
caData, err := dataSource.ReadCA(ctx, log, metaObj, r.namespace)
if apierrors.IsForbidden(err) {
log.V(logf.InfoLevel).Info("cainjector was forbidden to retrieve the ca data source")
return ctrl.Result{}, nil
}
if err != nil {
log.Error(err, "failed to read CA from data source")
return ctrl.Result{}, err
}
if caData == nil {
log.V(logf.InfoLevel).Info("could not find any ca data in data source for target")
return ctrl.Result{}, nil
}
// actually do the injection
target.SetCA(caData)
// actually update with injected CA data
if utilfeature.DefaultFeatureGate.Enabled(feature.ServerSideApply) {
obj, patch := target.AsApplyObject()
err = r.Client.Patch(ctx, obj, patch, &client.PatchOptions{
Force: pointer.Bool(true), FieldManager: r.fieldManager,
})
} else {
err = r.Client.Update(ctx, target.AsObject())
}
if err != nil {
log.Error(err, "unable to update target object with new CA data")
return ctrl.Result{}, err
}
log.V(logf.InfoLevel).Info("Updated object")
return ctrl.Result{}, nil
}
func (r *reconciler) caDataSourceFor(log logr.Logger, metaObj metav1.Object) (caDataSource, error) {
for _, s := range r.sources {
if s.Configured(log, metaObj) {
return s, nil
}
}
return nil, fmt.Errorf("could not determine ca data source for resource")
}
// dropNotFound ignores the given error if it's a not-found error,
// but otherwise just returns the argument.
// TODO: we don't use this pattern anywhere else in this project so probably doesn't make sense here either
func dropNotFound(err error) error {
if apierrors.IsNotFound(err) {
return nil
}
return err
}
// owningCertForSecret gets the name of the owning certificate for a
// given secret, returning nil if no such object exists.
func owningCertForSecret(secret *corev1.Secret) *types.NamespacedName {
val, ok := secret.Annotations[certmanager.CertificateNameKey]
if !ok {
return nil
}
return &types.NamespacedName{
Name: val,
Namespace: secret.Namespace,
}
}
// splitNamespacedName turns the string form of a namespaced name
// (<namespace>/<name>) back into a types.NamespacedName.
func splitNamespacedName(nameStr string) types.NamespacedName {
splitPoint := strings.IndexRune(nameStr, types.Separator)
if splitPoint == -1 {
return types.NamespacedName{Name: nameStr}
}
return types.NamespacedName{Namespace: nameStr[:splitPoint], Name: nameStr[splitPoint+1:]}
}