mirror of
https://github.com/mariadb-operator/mariadb-operator.git
synced 2025-07-25 01:28:31 +00:00
259 lines
8.7 KiB
Go
259 lines
8.7 KiB
Go
package controller
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/go-logr/logr"
|
|
mariadbv1alpha1 "github.com/mariadb-operator/mariadb-operator/api/v1alpha1"
|
|
certctrl "github.com/mariadb-operator/mariadb-operator/pkg/controller/certificate"
|
|
"github.com/mariadb-operator/mariadb-operator/pkg/health"
|
|
"github.com/mariadb-operator/mariadb-operator/pkg/metadata"
|
|
"github.com/mariadb-operator/mariadb-operator/pkg/pki"
|
|
"github.com/mariadb-operator/mariadb-operator/pkg/predicate"
|
|
admissionregistration "k8s.io/api/admissionregistration/v1"
|
|
v1 "k8s.io/api/core/v1"
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"k8s.io/client-go/tools/record"
|
|
ctrl "sigs.k8s.io/controller-runtime"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
"sigs.k8s.io/controller-runtime/pkg/handler"
|
|
"sigs.k8s.io/controller-runtime/pkg/log"
|
|
)
|
|
|
|
type WebhookConfigReconciler struct {
|
|
client.Client
|
|
scheme *runtime.Scheme
|
|
recorder record.EventRecorder
|
|
certReconciler *certctrl.CertReconciler
|
|
certOpts []certctrl.CertReconcilerOpt
|
|
serviceKey types.NamespacedName
|
|
requeueDuration time.Duration
|
|
leaderChan <-chan struct{}
|
|
leaderElected bool
|
|
readyMux *sync.Mutex
|
|
ready bool
|
|
}
|
|
|
|
func NewWebhookConfigReconciler(client client.Client, scheme *runtime.Scheme, recorder record.EventRecorder, leaderChan <-chan struct{},
|
|
caSecretKey types.NamespacedName, caCommonName string, caLifetime time.Duration,
|
|
certSecretKey types.NamespacedName, certLifetime time.Duration, renewBeforePercentage int32,
|
|
serviceKey types.NamespacedName, requeueDuration time.Duration) *WebhookConfigReconciler {
|
|
|
|
certOpts := []certctrl.CertReconcilerOpt{
|
|
certctrl.WithCA(true, caSecretKey),
|
|
certctrl.WithCACommonName(caCommonName),
|
|
certctrl.WithCALifetime(caLifetime),
|
|
certctrl.WithCASecretType(certctrl.SecretTypeTLS),
|
|
certctrl.WithCert(true, certSecretKey, serviceDNSNames(serviceKey).Names),
|
|
certctrl.WithCertLifetime(certLifetime),
|
|
certctrl.WithServerCertKeyUsage(),
|
|
certctrl.WithSupportedPrivateKeys(
|
|
pki.PrivateKeyTypeECDSA,
|
|
pki.PrivateKeyTypeRSA, // backwards compatibility with webhook certs from previous versions
|
|
),
|
|
certctrl.WithRenewBeforePercentage(renewBeforePercentage),
|
|
}
|
|
|
|
return &WebhookConfigReconciler{
|
|
Client: client,
|
|
scheme: scheme,
|
|
recorder: recorder,
|
|
certReconciler: certctrl.NewCertReconciler(client, scheme, recorder, nil, nil),
|
|
certOpts: certOpts,
|
|
serviceKey: serviceKey,
|
|
requeueDuration: requeueDuration,
|
|
leaderChan: leaderChan,
|
|
leaderElected: false,
|
|
readyMux: &sync.Mutex{},
|
|
ready: false,
|
|
}
|
|
}
|
|
|
|
func (r *WebhookConfigReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
|
certResult, err := r.certReconciler.Reconcile(ctx, r.certOpts...)
|
|
if err != nil {
|
|
return ctrl.Result{}, fmt.Errorf("error reconciling webhook certificate: %v", err)
|
|
}
|
|
|
|
if err := r.reconcileValidatingWebhook(ctx, req.NamespacedName, certResult); err != nil {
|
|
return ctrl.Result{}, fmt.Errorf("error reconciling ValidatingWebhookConfiguration: %v", err)
|
|
}
|
|
|
|
if err := r.reconcileMutatingWebhook(ctx, req.NamespacedName, certResult); err != nil {
|
|
return ctrl.Result{}, fmt.Errorf("error reconciling MutatingWebhookConfiguration: %v", err)
|
|
}
|
|
|
|
r.readyMux.Lock()
|
|
defer r.readyMux.Unlock()
|
|
r.ready = true
|
|
|
|
return ctrl.Result{
|
|
RequeueAfter: r.requeueDuration,
|
|
}, nil
|
|
}
|
|
|
|
func (r *WebhookConfigReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
|
return ctrl.NewControllerManagedBy(mgr).
|
|
Named("webhookconfiguration").
|
|
Watches(
|
|
&admissionregistration.ValidatingWebhookConfiguration{},
|
|
&handler.EnqueueRequestForObject{},
|
|
).
|
|
Watches(
|
|
&admissionregistration.MutatingWebhookConfiguration{},
|
|
&handler.EnqueueRequestForObject{},
|
|
).
|
|
WithEventFilter(predicate.PredicateWithAnnotations([]string{
|
|
metadata.WebhookConfigAnnotation,
|
|
})).
|
|
Complete(r)
|
|
}
|
|
|
|
func (r *WebhookConfigReconciler) ReadyHandler(logger logr.Logger) func(_ *http.Request) error {
|
|
return func(_ *http.Request) error {
|
|
if !r.leaderElected {
|
|
select {
|
|
case <-r.leaderChan:
|
|
r.leaderElected = true
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
r.readyMux.Lock()
|
|
defer r.readyMux.Unlock()
|
|
if !r.ready {
|
|
err := errors.New("webhook not ready")
|
|
logger.Error(err, "Readiness probe failed")
|
|
return err
|
|
}
|
|
healthy, err := health.IsServiceHealthy(context.Background(), r.Client, r.serviceKey)
|
|
if err != nil {
|
|
err := fmt.Errorf("service not ready: %s", err)
|
|
logger.Error(err, "Readiness probe failed")
|
|
return err
|
|
}
|
|
if !healthy {
|
|
err := errors.New("service not ready")
|
|
logger.Error(err, "Readiness probe failed")
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (r *WebhookConfigReconciler) reconcileValidatingWebhook(ctx context.Context, key types.NamespacedName,
|
|
certResult *certctrl.ReconcileResult) error {
|
|
logger := log.FromContext(ctx).WithValues("webhook", "validating")
|
|
var validatingWebhook admissionregistration.ValidatingWebhookConfiguration
|
|
if err := r.Get(ctx, key, &validatingWebhook); err != nil {
|
|
if apierrors.IsNotFound(err) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
logger.Info("Updating webhook config")
|
|
if err := r.patchValidatingWebhook(ctx, &validatingWebhook, func(cfg *admissionregistration.ValidatingWebhookConfiguration) {
|
|
r.injectValidatingWebhook(cfg, certResult.CAKeyPair.CertPEM, logger)
|
|
}); err != nil {
|
|
logger.Error(err, "Could not update ValidatingWebhookConfig")
|
|
r.recorder.Eventf(&validatingWebhook, v1.EventTypeWarning, mariadbv1alpha1.ReasonWebhookUpdateFailed, err.Error())
|
|
return err
|
|
}
|
|
logger.Info("Updated webhook config")
|
|
return nil
|
|
}
|
|
|
|
func (r *WebhookConfigReconciler) injectValidatingWebhook(cfg *admissionregistration.ValidatingWebhookConfiguration,
|
|
certData []byte, logger logr.Logger) {
|
|
logger.Info("Injecting CA certificate and service names", "name", cfg.Name)
|
|
for i := range cfg.Webhooks {
|
|
cfg.Webhooks[i].ClientConfig.Service.Name = r.serviceKey.Name
|
|
cfg.Webhooks[i].ClientConfig.Service.Namespace = r.serviceKey.Namespace
|
|
cfg.Webhooks[i].ClientConfig.CABundle = certData
|
|
}
|
|
}
|
|
|
|
func (r *WebhookConfigReconciler) patchValidatingWebhook(ctx context.Context, cfg *admissionregistration.ValidatingWebhookConfiguration,
|
|
patchFn func(cfg *admissionregistration.ValidatingWebhookConfiguration)) error {
|
|
patch := client.MergeFrom(cfg.DeepCopy())
|
|
patchFn(cfg)
|
|
if err := r.Patch(ctx, cfg, patch); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *WebhookConfigReconciler) reconcileMutatingWebhook(ctx context.Context, key types.NamespacedName,
|
|
certResult *certctrl.ReconcileResult) error {
|
|
logger := log.FromContext(ctx).WithValues("webhook", "mutating")
|
|
var mutatingWebhook admissionregistration.MutatingWebhookConfiguration
|
|
if err := r.Get(ctx, key, &mutatingWebhook); err != nil {
|
|
if apierrors.IsNotFound(err) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
logger.Info("Updating webhook config")
|
|
if err := r.patchMutatingWebhook(ctx, &mutatingWebhook, func(cfg *admissionregistration.MutatingWebhookConfiguration) {
|
|
r.injectMutatingWebhook(cfg, certResult.CAKeyPair.CertPEM, logger)
|
|
}); err != nil {
|
|
logger.Error(err, "Could not update MutatingWebhookConfig")
|
|
r.recorder.Eventf(&mutatingWebhook, v1.EventTypeWarning, mariadbv1alpha1.ReasonWebhookUpdateFailed, err.Error())
|
|
return err
|
|
}
|
|
logger.Info("Updated webhook config")
|
|
return nil
|
|
}
|
|
|
|
func (r *WebhookConfigReconciler) injectMutatingWebhook(cfg *admissionregistration.MutatingWebhookConfiguration,
|
|
certData []byte, logger logr.Logger) {
|
|
logger.Info("Injecting CA certificate and service names", "name", cfg.Name)
|
|
for i := range cfg.Webhooks {
|
|
cfg.Webhooks[i].ClientConfig.Service.Name = r.serviceKey.Name
|
|
cfg.Webhooks[i].ClientConfig.Service.Namespace = r.serviceKey.Namespace
|
|
cfg.Webhooks[i].ClientConfig.CABundle = certData
|
|
}
|
|
}
|
|
|
|
func (r *WebhookConfigReconciler) patchMutatingWebhook(ctx context.Context, cfg *admissionregistration.MutatingWebhookConfiguration,
|
|
patchFn func(cfg *admissionregistration.MutatingWebhookConfiguration)) error {
|
|
patch := client.MergeFrom(cfg.DeepCopy())
|
|
patchFn(cfg)
|
|
if err := r.Patch(ctx, cfg, patch); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type dnsNames struct {
|
|
CommonName string
|
|
Names []string
|
|
}
|
|
|
|
func serviceDNSNames(serviceKey types.NamespacedName) *dnsNames {
|
|
clusterName := os.Getenv("CLUSTER_NAME")
|
|
if clusterName == "" {
|
|
clusterName = "cluster.local"
|
|
}
|
|
commonName := fmt.Sprintf("%s.%s.svc", serviceKey.Name, serviceKey.Namespace)
|
|
return &dnsNames{
|
|
CommonName: commonName,
|
|
Names: []string{
|
|
fmt.Sprintf("%s.%s.svc.%s", serviceKey.Name, serviceKey.Namespace, clusterName),
|
|
commonName,
|
|
fmt.Sprintf("%s.%s", serviceKey.Name, serviceKey.Namespace),
|
|
serviceKey.Name,
|
|
},
|
|
}
|
|
}
|