From 78142604c049145e5a96173d8914e469dc653531 Mon Sep 17 00:00:00 2001 From: Jonathan Knight Date: Wed, 26 Feb 2025 16:46:03 +0300 Subject: [PATCH 1/4] Remove web-hook --- Makefile | 1 + PROJECT | 28 +- api/v1/coherence_webhook.go | 345 -------- api/v1/coherence_webhook_image_test.go | 252 ------ api/v1/coherence_webhook_job.go | 163 ---- api/v1/coherence_webhook_job_test.go | 691 ---------------- api/v1/coherence_webhook_test.go | 777 ------------------ api/v1/validate.go | 55 ++ api/v1/zz_generated.deepcopy.go | 17 +- config/crd/kustomization.yaml | 13 +- config/crd/kustomizeconfig.yaml | 24 +- .../crd/patches/cainjection_in_coherence.yaml | 8 - config/crd/patches/webhook_in_coherence.yaml | 20 - config/default/config.yaml | 3 - config/default/kustomization.yaml | 36 +- config/default/manager_metrics_patch.yaml | 4 + config/default/metrics_service.yaml | 17 + config/manager/kustomization.yaml | 1 - config/manager/manager.yaml | 19 - config/manager/service.yaml | 23 - config/manager/webhook-secret.yaml | 11 - config/manifests/kustomization.yaml | 33 +- .../network-policy/allow-metrics-traffic.yaml | 26 + config/network-policy/kustomization.yaml | 2 + config/overlays/restricted/cluster_role.yaml | 9 - .../restricted/cluster_role_binding.yaml | 5 - config/overlays/restricted/kustomization.yaml | 2 - .../restricted/single-namespace-patch.yaml | 3 - config/prometheus/kustomization.yaml | 2 + config/prometheus/monitor.yaml | 30 + config/rbac/cluster_role.yaml | 31 - config/rbac/cluster_role_binding.yaml | 18 - config/rbac/coherence_editor_role.yaml | 41 +- config/rbac/coherence_viewer_role.yaml | 33 +- config/rbac/coherencejob_editor_role.yaml | 27 + config/rbac/coherencejob_viewer_role.yaml | 23 + config/rbac/kustomization.yaml | 38 +- config/rbac/leader_election_role.yaml | 67 +- config/rbac/leader_election_role_binding.yaml | 9 +- config/rbac/metrics_auth_role.yaml | 17 + config/rbac/metrics_auth_role_binding.yaml | 12 + config/rbac/metrics_reader_role.yaml | 9 + config/rbac/node_viewer_role.yaml | 5 +- config/rbac/node_viewer_role_binding.yaml | 3 +- config/rbac/role_binding.yaml | 5 +- config/rbac/service_account.yaml | 6 +- config/samples/coherence_v1_coherencejob.yaml | 10 + config/samples/kustomization.yaml | 6 +- config/scorecard/kustomization.yaml | 32 +- config/scorecard/patches/basic.config.yaml | 6 +- config/scorecard/patches/olm.config.yaml | 30 +- controllers/coherence_controller.go | 45 +- controllers/coherencejob_controller.go | 6 +- controllers/common.go | 6 +- controllers/job/job_controller.go | 4 +- .../servicemonitor_controller.go | 15 +- controllers/webhook/webhook.go | 503 ------------ controllers/webhook/webhook_controller.go | 273 ------ go.mod | 75 +- go.sum | 173 ++-- .../templates/deployment.yaml | 70 -- .../coherence-operator/templates/rbac.yaml | 13 - helm-charts/coherence-operator/values.yaml | 17 - pkg/certs/certs.go | 280 ------- pkg/operator/operator.go | 195 +---- pkg/runner/cmd_operator.go | 29 +- 66 files changed, 674 insertions(+), 4078 deletions(-) delete mode 100644 api/v1/coherence_webhook.go delete mode 100644 api/v1/coherence_webhook_image_test.go delete mode 100644 api/v1/coherence_webhook_job.go delete mode 100644 api/v1/coherence_webhook_job_test.go delete mode 100644 api/v1/coherence_webhook_test.go create mode 100644 api/v1/validate.go delete mode 100644 config/crd/patches/cainjection_in_coherence.yaml delete mode 100644 config/crd/patches/webhook_in_coherence.yaml delete mode 100644 config/default/config.yaml create mode 100644 config/default/manager_metrics_patch.yaml create mode 100644 config/default/metrics_service.yaml delete mode 100644 config/manager/webhook-secret.yaml create mode 100644 config/network-policy/allow-metrics-traffic.yaml create mode 100644 config/network-policy/kustomization.yaml delete mode 100644 config/overlays/restricted/cluster_role.yaml delete mode 100644 config/overlays/restricted/cluster_role_binding.yaml create mode 100644 config/prometheus/kustomization.yaml create mode 100644 config/prometheus/monitor.yaml delete mode 100644 config/rbac/cluster_role.yaml delete mode 100644 config/rbac/cluster_role_binding.yaml create mode 100644 config/rbac/coherencejob_editor_role.yaml create mode 100644 config/rbac/coherencejob_viewer_role.yaml create mode 100644 config/rbac/metrics_auth_role.yaml create mode 100644 config/rbac/metrics_auth_role_binding.yaml create mode 100644 config/rbac/metrics_reader_role.yaml create mode 100644 config/samples/coherence_v1_coherencejob.yaml delete mode 100644 controllers/webhook/webhook.go delete mode 100644 controllers/webhook/webhook_controller.go delete mode 100644 pkg/certs/certs.go diff --git a/Makefile b/Makefile index fe8849fa6..a6b2af3d6 100644 --- a/Makefile +++ b/Makefile @@ -297,6 +297,7 @@ TOOLS_BIN = $(TOOLS_DIRECTORY)/bin TOOLS_MANIFESTS = $(TOOLS_DIRECTORY)/manifests OPERATOR_SDK_HOME = $(TOOLS_DIRECTORY)/sdk/$(UNAME_S)-$(UNAME_M) OPERATOR_SDK = $(OPERATOR_SDK_HOME)/operator-sdk +ENVTEST = $(TOOLS_BIN)/setup-envtest # ---------------------------------------------------------------------------------------------------------------------- # The ttl.sh images used in integration tests diff --git a/PROJECT b/PROJECT index f9d815ef0..789642c41 100644 --- a/PROJECT +++ b/PROJECT @@ -1,17 +1,35 @@ +# Code generated by tool. DO NOT EDIT. +# This file is used to track the info used to scaffold your project +# and allow the plugins properly work. +# More info: https://book.kubebuilder.io/reference/project-config.html domain: oracle.com layout: - go.kubebuilder.io/v4 +multigroup: true +plugins: + manifests.sdk.operatorframework.io/v2: {} + scorecard.sdk.operatorframework.io/v2: {} projectName: coherence-operator repo: github.com/oracle/coherence-operator resources: -- +- api: + crdVersion: v1 + namespaced: true controller: true domain: oracle.com - group: coherence.oracle.com + group: coherence kind: Coherence path: github.com/oracle/coherence-operator/api/v1 + plural: coherence + version: v1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: oracle.com + group: coherence + kind: CoherenceJob + path: github.com/oracle/coherence-operator/api/v1 + plural: coherencejob version: v1 version: "3" -plugins: - manifests.sdk.operatorframework.io/v2: {} - scorecard.sdk.operatorframework.io/v2: {} diff --git a/api/v1/coherence_webhook.go b/api/v1/coherence_webhook.go deleted file mode 100644 index d94f333ce..000000000 --- a/api/v1/coherence_webhook.go +++ /dev/null @@ -1,345 +0,0 @@ -/* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. - * Licensed under the Universal Permissive License v 1.0 as shown at - * http://oss.oracle.com/licenses/upl. - */ - -package v1 - -import ( - "fmt" - "github.com/distribution/reference" - "github.com/go-test/deep" - "github.com/oracle/coherence-operator/pkg/operator" - "github.com/pkg/errors" - appsv1 "k8s.io/api/apps/v1" - apiequality "k8s.io/apimachinery/pkg/api/equality" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/utils/ptr" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/webhook" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" -) - -// log is for logging in this package. -var ( - webhookLogger = logf.Log.WithName("coherence-webhook") -) - -func (in *Coherence) SetupWebhookWithManager(mgr ctrl.Manager) error { - return ctrl.NewWebhookManagedBy(mgr). - For(in). - Complete() -} - -// The path in this annotation MUST match the const below -// +kubebuilder:webhook:path=/mutate-coherence-oracle-com-v1-coherence,mutating=true,failurePolicy=fail,groups=coherence.oracle.com,resources=coherence,verbs=create;update,versions=v1,name=mcoherence.kb.io - -// MutatingWebHookPath This const MUST match the path in the kubebuilder annotation above -const MutatingWebHookPath = "/mutate-coherence-oracle-com-v1-coherence" - -// An anonymous var to ensure that the Coherence struct implements webhook.Defaulter -// there will be a compile time error here if this fails. -var _ webhook.Defaulter = &Coherence{} - -// Default implements webhook.Defaulter so a webhook will be registered for the type -func (in *Coherence) Default() { - spec, _ := in.GetStatefulSetSpec() - // set the default replicas if not present - if spec.Replicas == nil { - spec.SetReplicas(spec.GetReplicas()) - } - SetCommonDefaults(in) - - // apply a label with the hash of the spec - ths must be the last action here to make sure that - // any modifications to the spec field are included in the hash - if hash, applied := EnsureCoherenceHashLabel(in); applied { - webhookLogger.Info(fmt.Sprintf("Applied %s label", LabelCoherenceHash), "hash", hash) - } -} - -// SetCommonDefaults sets defaults common to both a Job and StatefulSet -func SetCommonDefaults(in CoherenceResource) { - logger := webhookLogger.WithValues("namespace", in.GetNamespace(), "name", in.GetName()) - status := in.GetStatus() - spec := in.GetSpec() - dt := in.GetDeletionTimestamp() - - if dt != nil { - // the deletion timestamp is set so do nothing - logger.Info("Skipping updating defaults for deleted resource", "deletionTimestamp", *dt) - return - } - - if status.Phase == "" { - logger.Info("Setting defaults for new resource") - - stsSpec, found := in.GetStatefulSetSpec() - if found { - // ensure the operator finalizer is present - if stsSpec.AllowUnsafeDelete != nil && *stsSpec.AllowUnsafeDelete { - if controllerutil.ContainsFinalizer(in, CoherenceFinalizer) { - controllerutil.RemoveFinalizer(in, CoherenceFinalizer) - logger.Info("Removed Finalizer from Coherence resource as AllowUnsafeDelete has been set to true") - } else { - logger.Info("Finalizer not added to Coherence resource as AllowUnsafeDelete has been set to true") - } - } else { - controllerutil.AddFinalizer(in, CoherenceFinalizer) - } - } - - // set the default Coherence local port and local port adjust if not present - if spec.Coherence == nil { - var lpa = intstr.FromInt32(DefaultUnicastPortAdjust) - spec.Coherence = &CoherenceSpec{ - LocalPort: ptr.To(DefaultUnicastPort), - LocalPortAdjust: &lpa, - } - } else { - if spec.Coherence.LocalPort == nil { - spec.Coherence.LocalPort = ptr.To(DefaultUnicastPort) - } - if spec.Coherence.LocalPortAdjust == nil { - lpa := intstr.FromInt32(DefaultUnicastPortAdjust) - spec.Coherence.LocalPortAdjust = &lpa - } - } - - // only set defaults for image names in new Coherence instances - coherenceImage := operator.GetDefaultCoherenceImage() - spec.EnsureCoherenceImage(&coherenceImage) - operatorImage := operator.GetDefaultOperatorImage() - spec.EnsureCoherenceOperatorImage(&operatorImage) - - // Set the features supported by this version - in.AddAnnotation(AnnotationFeatureSuspend, "true") - } else { - // this is an update - logger.Info("Updating defaults for existing resource") - } - - // apply the Operator version annotation - in.AddAnnotation(AnnotationOperatorVersion, operator.GetVersion()) -} - -// The path in this annotation MUST match the const below -// +kubebuilder:webhook:verbs=create;update,path=/validate-coherence-oracle-com-v1-coherence,mutating=false,failurePolicy=fail,groups=coherence.oracle.com,resources=coherence,versions=v1,name=vcoherence.kb.io - -// ValidatingWebHookPath This const MUST match the path in the kubebuilder annotation above -const ValidatingWebHookPath = "/validate-coherence-oracle-com-v1-coherence" - -// An anonymous var to ensure that the Coherence struct implements webhook.Validator -// there will be a compile time error here if this fails. -var _ webhook.Validator = &Coherence{} -var commonWebHook = CommonWebHook{} - -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type -// The optional warnings will be added to the response as warning messages. -// Return an error if the object is invalid. -func (in *Coherence) ValidateCreate() (admission.Warnings, error) { - logger := webhookLogger.WithValues("namespace", in.GetNamespace(), "name", in.GetName()) - var warnings admission.Warnings - - dt := in.GetDeletionTimestamp() - if dt != nil { - // the deletion timestamp is set so do nothing - logger.Info("Skipping validation for deleted resource", "deletionTimestamp", *dt) - return warnings, nil - } - - webhookLogger.Info("validate create", "name", in.Name) - if err := commonWebHook.validateReplicas(in); err != nil { - return warnings, err - } - if err := commonWebHook.validateImages(in); err != nil { - return warnings, err - } - if err := commonWebHook.validateNodePorts(in); err != nil { - return warnings, err - } - return warnings, nil -} - -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -// The optional warnings will be added to the response as warning messages. -// Return an error if the object is invalid. -func (in *Coherence) ValidateUpdate(previous runtime.Object) (admission.Warnings, error) { - webhookLogger.Info("validate update", "name", in.Name) - logger := webhookLogger.WithValues("namespace", in.GetNamespace(), "name", in.GetName()) - var warnings admission.Warnings - - dt := in.GetDeletionTimestamp() - if dt != nil { - // the deletion timestamp is set so do nothing - logger.Info("Skipping validation for deleted resource", "deletionTimestamp", *dt) - return warnings, nil - } - - if err := commonWebHook.validateReplicas(in); err != nil { - return warnings, err - } - if err := commonWebHook.validateImages(in); err != nil { - return warnings, err - } - prev := previous.(*Coherence) - - if err := commonWebHook.validatePersistence(in, prev); err != nil { - return warnings, err - } - if err := in.validateVolumeClaimTemplates(prev); err != nil { - return warnings, err - } - if err := commonWebHook.validateNodePorts(in); err != nil { - return warnings, err - } - - var errorList field.ErrorList - sts := in.Spec.CreateStatefulSet(in) - stsOld := prev.Spec.CreateStatefulSet(prev) - errorList = ValidateStatefulSetUpdate(&sts, &stsOld) - - if len(errorList) > 0 { - return warnings, fmt.Errorf("rejecting update as it would have resulted in an invalid StatefulSet: %v", errorList) - } - - return warnings, nil -} - -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type -// The optional warnings will be added to the response as warning messages. -// Return an error if the object is invalid. -func (in *Coherence) ValidateDelete() (admission.Warnings, error) { - // we do not need to validate deletions - return nil, nil -} - -func (in *Coherence) validateVolumeClaimTemplates(previous *Coherence) error { - if in.GetReplicas() == 0 || previous.GetReplicas() == 0 { - // changes are allowed if current or previous replicas == 0 - return nil - } - - if len(in.Spec.VolumeClaimTemplates) == 0 && len(previous.Spec.VolumeClaimTemplates) == 0 { - // no PVCs in either deployment - return nil - } - - diff := deep.Equal(previous.Spec.VolumeClaimTemplates, in.Spec.VolumeClaimTemplates) - if len(diff) != 0 { - return fmt.Errorf("the Coherence resource \"%s\" is invalid: "+ - "changes cannot be made to spec.volumeclaimtemplates unless spec.replicas == 0 or the previous"+ - " instance of the resource has spec.replicas == 0", in.Name) - } - return nil -} - -// ----- Common Validator --------------------------------------------------- - -type CommonWebHook struct { -} - -// validateImages validates image names -func (in *CommonWebHook) validateImages(c CoherenceResource) error { - var err error - spec := c.GetSpec() - if spec != nil { - img := spec.GetCoherenceImage() - if img != nil { - _, err = reference.Parse(*img) - if err != nil { - return errors.Errorf("invalid spec.image field, %s", err.Error()) - } - } - img = spec.GetCoherenceOperatorImage() - if img != nil { - _, err = reference.Parse(*img) - if err != nil { - return errors.Errorf("invalid spec.coherenceUtils.image field, %s", err.Error()) - } - } - for _, c := range spec.InitContainers { - _, err = reference.Parse(c.Image) - if err != nil { - return errors.Errorf("invalid image name in init-container %s, %s", c.Name, err.Error()) - } - } - for _, c := range spec.SideCars { - _, err = reference.Parse(c.Image) - if err != nil { - return errors.Errorf("invalid image name in side-car container %s, %s", c.Name, err.Error()) - } - } - } - return err -} - -// validateReplicas validates that spec.replicas >= 0 -func (in *CommonWebHook) validateReplicas(c CoherenceResource) error { - replicas := c.GetReplicas() - if replicas < 0 { - return fmt.Errorf("the Coherence resource \"%s\" is invalid: spec.replicas: Invalid value: %d: "+ - "must be greater than or equal to 0", c.GetName(), replicas) - } - return nil -} - -func (in *CommonWebHook) validatePersistence(current, previous CoherenceResource) error { - if current.GetReplicas() == 0 || previous.GetReplicas() == 0 { - // changes are allowed if current or previous replicas == 0 - return nil - } - - currentSpec := current.GetSpec() - previousSpec := previous.GetSpec() - diff := deep.Equal(previousSpec.GetCoherencePersistence(), currentSpec.GetCoherencePersistence()) - if len(diff) != 0 { - return fmt.Errorf("the Coherence resource \"%s\" is invalid: "+ - "changes cannot be made to spec.coherence.persistence unless spec.replicas == 0 or the previous"+ - " instance of the resource has spec.replicas == 0", current.GetName()) - } - return nil -} - -func (in *CommonWebHook) validateNodePorts(current CoherenceResource) error { - var badPorts []string - spec := current.GetSpec() - for _, port := range spec.Ports { - if port.NodePort != nil { - p := *port.NodePort - if p < 30000 || p > 32767 { - badPorts = append(badPorts, port.Name) - } - } - } - - if len(badPorts) > 0 { - return fmt.Errorf("the following NodePort values are invalid, valid port range is 30000-32767 - %v", badPorts) - } - - return nil -} - -// ValidateStatefulSetUpdate tests if required fields in the StatefulSet are set. -func ValidateStatefulSetUpdate(statefulSet, oldStatefulSet *appsv1.StatefulSet) field.ErrorList { - var allErrs field.ErrorList - - // statefulset updates aren't super common and general updates are likely to be touching spec, so we'll do this - // deep copy right away. This avoids mutating our inputs - newStatefulSetClone := statefulSet.DeepCopy() - newStatefulSetClone.Spec.Replicas = oldStatefulSet.Spec.Replicas // +k8s:verify-mutation:reason=clone - newStatefulSetClone.Spec.Template = oldStatefulSet.Spec.Template // +k8s:verify-mutation:reason=clone - newStatefulSetClone.Spec.UpdateStrategy = oldStatefulSet.Spec.UpdateStrategy // +k8s:verify-mutation:reason=clone - newStatefulSetClone.Spec.MinReadySeconds = oldStatefulSet.Spec.MinReadySeconds // +k8s:verify-mutation:reason=clone - - newStatefulSetClone.Spec.PersistentVolumeClaimRetentionPolicy = oldStatefulSet.Spec.PersistentVolumeClaimRetentionPolicy // +k8s:verify-mutation:reason=clone - if !apiequality.Semantic.DeepEqual(newStatefulSetClone.Spec, oldStatefulSet.Spec) { - allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "updates to statefulset spec for fields other than 'replicas', 'template', 'updateStrategy', 'persistentVolumeClaimRetentionPolicy' and 'minReadySeconds' are forbidden")) - } - - return allErrs -} diff --git a/api/v1/coherence_webhook_image_test.go b/api/v1/coherence_webhook_image_test.go deleted file mode 100644 index acd40f027..000000000 --- a/api/v1/coherence_webhook_image_test.go +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright (c) 2023, Oracle and/or its affiliates. - * Licensed under the Universal Permissive License v 1.0 as shown at - * http://oss.oracle.com/licenses/upl. - */ - -package v1_test - -import ( - . "github.com/onsi/gomega" - coh "github.com/oracle/coherence-operator/api/v1" - corev1 "k8s.io/api/core/v1" - "testing" -) - -// Tests for image name validation - -func TestCoherenceWithNoImageNames(t *testing.T) { - g := NewGomegaWithT(t) - - c := coh.Coherence{} - _, err := c.ValidateCreate() - g.Expect(err).NotTo(HaveOccurred()) -} - -func TestCoherenceCreateWithValidImageName(t *testing.T) { - g := NewGomegaWithT(t) - - c := coh.Coherence{ - Spec: coh.CoherenceStatefulSetResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Image: stringPtr("test/coherence:1.0"), - }, - }, - } - _, err := c.ValidateCreate() - g.Expect(err).NotTo(HaveOccurred()) -} - -func TestCoherenceCreateWithInvalidImageName(t *testing.T) { - g := NewGomegaWithT(t) - - c := coh.Coherence{ - Spec: coh.CoherenceStatefulSetResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Image: stringPtr("test/bad image name:1.0"), - }, - }, - } - _, err := c.ValidateCreate() - g.Expect(err).To(HaveOccurred()) -} - -func TestCoherenceCreateWithImageNameWithTrailingSpace(t *testing.T) { - g := NewGomegaWithT(t) - - c := coh.Coherence{ - Spec: coh.CoherenceStatefulSetResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Image: stringPtr("test/coherence:1.0 "), - }, - }, - } - _, err := c.ValidateCreate() - g.Expect(err).To(HaveOccurred()) -} - -func TestCoherenceCreateWithValidOperatorImageName(t *testing.T) { - g := NewGomegaWithT(t) - - c := coh.Coherence{ - Spec: coh.CoherenceStatefulSetResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - CoherenceUtils: &coh.ImageSpec{ - Image: stringPtr("test/coherence:1.0"), - }, - }, - }, - } - _, err := c.ValidateCreate() - g.Expect(err).NotTo(HaveOccurred()) -} - -func TestCoherenceCreateWithInvalidOperatorImageName(t *testing.T) { - g := NewGomegaWithT(t) - - c := coh.Coherence{ - Spec: coh.CoherenceStatefulSetResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - CoherenceUtils: &coh.ImageSpec{ - Image: stringPtr("test/bad image name:1.0"), - }, - }, - }, - } - _, err := c.ValidateCreate() - g.Expect(err).To(HaveOccurred()) -} - -func TestCoherenceUpdateWithInvalidImageName(t *testing.T) { - g := NewGomegaWithT(t) - - c := coh.Coherence{ - Spec: coh.CoherenceStatefulSetResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Image: stringPtr("test/bad image name:1.0"), - }, - }, - } - _, err := c.ValidateUpdate(&c) - g.Expect(err).To(HaveOccurred()) -} - -func TestCoherenceCreateWithValidInitContainerImageName(t *testing.T) { - g := NewGomegaWithT(t) - - c := coh.Coherence{ - Spec: coh.CoherenceStatefulSetResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - InitContainers: []corev1.Container{ - { - Name: "side-one", - Image: "test/coherence:1.0", - }, - }, - }, - }, - } - _, err := c.ValidateCreate() - g.Expect(err).NotTo(HaveOccurred()) -} - -func TestCoherenceCreateWithInvalidInitContainerImageName(t *testing.T) { - g := NewGomegaWithT(t) - - c := coh.Coherence{ - Spec: coh.CoherenceStatefulSetResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - InitContainers: []corev1.Container{ - { - Name: "side-one", - Image: "test/bad image name:1.0", - }, - }, - }, - }, - } - _, err := c.ValidateCreate() - g.Expect(err).To(HaveOccurred()) -} - -func TestCoherenceCreateWithValidSidecarImageName(t *testing.T) { - g := NewGomegaWithT(t) - - c := coh.Coherence{ - Spec: coh.CoherenceStatefulSetResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - SideCars: []corev1.Container{ - { - Name: "side-one", - Image: "test/coherence:1.0", - }, - }, - }, - }, - } - _, err := c.ValidateCreate() - g.Expect(err).NotTo(HaveOccurred()) -} - -func TestCoherenceCreateWithInvalidSidecarImageName(t *testing.T) { - g := NewGomegaWithT(t) - - c := coh.Coherence{ - Spec: coh.CoherenceStatefulSetResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - SideCars: []corev1.Container{ - { - Name: "side-one", - Image: "test/bad image name:1.0", - }, - }, - }, - }, - } - _, err := c.ValidateCreate() - g.Expect(err).To(HaveOccurred()) -} - -func TestJobWithNoImageNames(t *testing.T) { - g := NewGomegaWithT(t) - - c := coh.CoherenceJob{} - _, err := c.ValidateCreate() - g.Expect(err).NotTo(HaveOccurred()) -} - -func TestJobCreateWithInvalidImageName(t *testing.T) { - g := NewGomegaWithT(t) - - c := coh.CoherenceJob{ - Spec: coh.CoherenceJobResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Image: stringPtr("test/bad image name:1.0"), - }, - }, - } - _, err := c.ValidateCreate() - g.Expect(err).To(HaveOccurred()) -} - -func TestJobCreateWithValidImageDigest(t *testing.T) { - g := NewGomegaWithT(t) - - c := coh.CoherenceJob{ - Spec: coh.CoherenceJobResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Image: stringPtr("ghcr.io@sha256:f8a592ee6d31c02feea037c269a87564ae666f91480d1d6be24ff9dd1675c7d0"), - }, - }, - } - _, err := c.ValidateCreate() - g.Expect(err).NotTo(HaveOccurred()) -} - -func TestJobCreateWithInvalidImageDigest(t *testing.T) { - g := NewGomegaWithT(t) - - c := coh.CoherenceJob{ - Spec: coh.CoherenceJobResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Image: stringPtr("test@sha256:1234"), - }, - }, - } - _, err := c.ValidateCreate() - g.Expect(err).To(HaveOccurred()) -} - -func TestJobUpdateWithInvalidImageName(t *testing.T) { - g := NewGomegaWithT(t) - - c := coh.CoherenceJob{ - Spec: coh.CoherenceJobResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Image: stringPtr("test/bad image name:1.0"), - }, - }, - } - _, err := c.ValidateUpdate(&c) - g.Expect(err).To(HaveOccurred()) -} diff --git a/api/v1/coherence_webhook_job.go b/api/v1/coherence_webhook_job.go deleted file mode 100644 index 9e057bfac..000000000 --- a/api/v1/coherence_webhook_job.go +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. - * Licensed under the Universal Permissive License v 1.0 as shown at - * http://oss.oracle.com/licenses/upl. - */ - -package v1 - -import ( - "fmt" - batchv1 "k8s.io/api/batch/v1" - corev1 "k8s.io/api/core/v1" - apiequality "k8s.io/apimachinery/pkg/api/equality" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/utils/ptr" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/webhook" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" -) - -func (in *CoherenceJob) SetupWebhookWithManager(mgr ctrl.Manager) error { - return ctrl.NewWebhookManagedBy(mgr). - For(in). - Complete() -} - -// The path in this annotation MUST match the const below -// +kubebuilder:webhook:path=/mutate-coherence-oracle-com-v1-coherencejob,mutating=true,failurePolicy=fail,groups=coherence.oracle.com,resources=coherencejob,verbs=create;update,versions=v1,name=mcoherencejob.kb.io - -// JobMutatingWebHookPath This const MUST match the path in the kubebuilder annotation above -const JobMutatingWebHookPath = "/mutate-coherence-oracle-com-v1-coherencejob" - -// An anonymous var to ensure that the CoherenceJob struct implements webhook.Defaulter -// there will be a compile time error here if this fails. -var _ webhook.Defaulter = &CoherenceJob{} - -// Default implements webhook.Defaulter so a webhook will be registered for the type -func (in *CoherenceJob) Default() { - spec, _ := in.GetJobResourceSpec() - coherenceSpec := spec.Coherence - if spec.Coherence == nil { - coherenceSpec = &CoherenceSpec{} - spec.Coherence = coherenceSpec - } - - // default to storage disabled to false - if coherenceSpec.StorageEnabled == nil { - coherenceSpec.StorageEnabled = ptr.To(false) - } - - // default the restart policy to never - if spec.RestartPolicy == nil { - spec.RestartPolicy = spec.RestartPolicyPointer(corev1.RestartPolicyNever) - } - - co := spec.Coherence - if co != nil { - if co.StorageEnabled == nil { - co.StorageEnabled = ptr.To(false) - } - } - - // set the default replicas if not present - if spec.CoherenceResourceSpec.Replicas == nil { - spec.SetReplicas(spec.GetReplicas()) - } - - SetCommonDefaults(in) - - // apply a label with the hash of the spec - ths must be the last action here to make sure that - // any modifications to the spec field are included in the hash - if hash, applied := EnsureJobHashLabel(in); applied { - webhookLogger.Info(fmt.Sprintf("Applied %s label", LabelCoherenceHash), "hash", hash) - } -} - -// The path in this annotation MUST match the const below -// +kubebuilder:webhook:verbs=create;update,path=/validate-coherence-oracle-com-v1-coherencejob,mutating=false,failurePolicy=fail,groups=coherence.oracle.com,resources=coherencejob,versions=v1,name=vcoherencejob.kb.io - -// JobValidatingWebHookPath This const MUST match the path in the kubebuilder annotation above -const JobValidatingWebHookPath = "/validate-coherence-oracle-com-v1-coherencejob" - -// An anonymous var to ensure that the Coherence struct implements webhook.Validator -// there will be a compile time error here if this fails. -var _ webhook.Validator = &CoherenceJob{} - -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (in *CoherenceJob) ValidateCreate() (admission.Warnings, error) { - var err error - var warnings admission.Warnings - - webhookLogger.Info("validate create", "name", in.Name) - if err = commonWebHook.validateReplicas(in); err != nil { - return warnings, err - } - if err = commonWebHook.validateImages(in); err != nil { - return warnings, err - } - if err = commonWebHook.validateNodePorts(in); err != nil { - return warnings, err - } - return warnings, nil -} - -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (in *CoherenceJob) ValidateUpdate(previous runtime.Object) (admission.Warnings, error) { - webhookLogger.Info("validate update", "name", in.Name) - var warnings admission.Warnings - - if err := commonWebHook.validateReplicas(in); err != nil { - return warnings, err - } - if err := commonWebHook.validateImages(in); err != nil { - return warnings, err - } - prev := previous.(*CoherenceJob) - - if err := commonWebHook.validatePersistence(in, prev); err != nil { - return warnings, err - } - if err := commonWebHook.validateNodePorts(in); err != nil { - return warnings, err - } - - var errorList field.ErrorList - job := in.Spec.CreateJob(in) - jobOld := prev.Spec.CreateJob(prev) - errorList = ValidateJobUpdate(&job, &jobOld) - - if len(errorList) > 0 { - return warnings, fmt.Errorf("rejecting update as it would have resulted in an invalid Job: %v", errorList) - } - - return warnings, nil -} - -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (in *CoherenceJob) ValidateDelete() (admission.Warnings, error) { - // we do not need to validate deletions - return nil, nil -} - -// ValidateJobUpdate tests if required fields in the Job are set. -func ValidateJobUpdate(job, oldJob *batchv1.Job) field.ErrorList { - var allErrs field.ErrorList - - newJobClone := job.DeepCopy() - newJobClone.Spec.ActiveDeadlineSeconds = oldJob.Spec.ActiveDeadlineSeconds // +k8s:verify-mutation:reason=clone - newJobClone.Spec.BackoffLimit = oldJob.Spec.BackoffLimit // +k8s:verify-mutation:reason=clone - newJobClone.Spec.CompletionMode = oldJob.Spec.CompletionMode // +k8s:verify-mutation:reason=clone - newJobClone.Spec.Parallelism = oldJob.Spec.Parallelism // +k8s:verify-mutation:reason=clone - newJobClone.Spec.Suspend = oldJob.Spec.Suspend // +k8s:verify-mutation:reason=clone - newJobClone.Spec.Template = oldJob.Spec.Template // +k8s:verify-mutation:reason=clone - newJobClone.Spec.TTLSecondsAfterFinished = oldJob.Spec.TTLSecondsAfterFinished // +k8s:verify-mutation:reason=clone - newJobClone.Spec.PodFailurePolicy = oldJob.Spec.PodFailurePolicy // +k8s:verify-mutation:reason=clone - newJobClone.Spec.Completions = oldJob.Spec.Completions // +k8s:verify-mutation:reason=clone - - if !apiequality.Semantic.DeepEqual(newJobClone.Spec, oldJob.Spec) { - allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "updates to Job spec for fields 'selector', 'manualSelector', are forbidden")) - } - return allErrs -} diff --git a/api/v1/coherence_webhook_job_test.go b/api/v1/coherence_webhook_job_test.go deleted file mode 100644 index 072200180..000000000 --- a/api/v1/coherence_webhook_job_test.go +++ /dev/null @@ -1,691 +0,0 @@ -/* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. - * Licensed under the Universal Permissive License v 1.0 as shown at - * http://oss.oracle.com/licenses/upl. - */ - -package v1_test - -import ( - . "github.com/onsi/gomega" - coh "github.com/oracle/coherence-operator/api/v1" - "github.com/oracle/coherence-operator/pkg/operator" - "github.com/spf13/viper" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/utils/ptr" - "testing" -) - -func TestJobDefaultReplicasIsSet(t *testing.T) { - g := NewGomegaWithT(t) - - c := coh.CoherenceJob{} - c.Default() - g.Expect(c.Spec.Replicas).NotTo(BeNil()) - g.Expect(*c.Spec.Replicas).To(Equal(coh.DefaultJobReplicas)) -} - -func TestJobAddVersionAnnotation(t *testing.T) { - g := NewGomegaWithT(t) - - c := coh.CoherenceJob{} - c.Default() - g.Expect(c.Annotations).NotTo(BeNil()) - g.Expect(c.Annotations[coh.AnnotationOperatorVersion]).To(Equal(operator.GetVersion())) - g.Expect(c.Spec).NotTo(BeNil()) - replicas := c.Spec.CoherenceResourceSpec.Replicas - g.Expect(*replicas).To(Equal(coh.DefaultJobReplicas)) -} - -func TestJobShouldAddFinalizer(t *testing.T) { - g := NewGomegaWithT(t) - c := coh.CoherenceJob{} - c.Default() - finalizers := c.GetFinalizers() - g.Expect(len(finalizers)).To(Equal(0)) -} - -func TestJobShouldNotAddFinalizerAgainIfPresent(t *testing.T) { - g := NewGomegaWithT(t) - c := coh.CoherenceJob{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Finalizers: []string{coh.CoherenceFinalizer}, - }, - } - c.Default() - finalizers := c.GetFinalizers() - g.Expect(len(finalizers)).To(Equal(1)) - g.Expect(finalizers).To(ContainElement(coh.CoherenceFinalizer)) -} - -func TestJobShouldNotRemoveFinalizersAlreadyPresent(t *testing.T) { - g := NewGomegaWithT(t) - c := coh.CoherenceJob{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Finalizers: []string{"foo", "bar"}, - }, - } - c.Default() - finalizers := c.GetFinalizers() - g.Expect(len(finalizers)).To(Equal(2)) - g.Expect(finalizers).To(ContainElement("foo")) - g.Expect(finalizers).To(ContainElement("bar")) -} - -func TestJobDefaultReplicasIsNotOverriddenWhenAlreadySet(t *testing.T) { - g := NewGomegaWithT(t) - - var replicas int32 = 19 - c := coh.CoherenceJob{ - Spec: coh.CoherenceJobResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Replicas: &replicas, - }, - }, - } - c.Default() - g.Expect(c.Spec.Replicas).NotTo(BeNil()) - g.Expect(*c.Spec.Replicas).To(Equal(replicas)) -} - -func TestJobCoherenceLocalPortIsSet(t *testing.T) { - g := NewGomegaWithT(t) - - c := coh.CoherenceJob{} - c.Default() - g.Expect(c.Spec.Coherence).NotTo(BeNil()) - g.Expect(*c.Spec.Coherence.LocalPort).To(Equal(coh.DefaultUnicastPort)) -} - -func TestJobCoherenceLocalPortIsNotOverridden(t *testing.T) { - g := NewGomegaWithT(t) - - var port int32 = 1234 - - c := coh.CoherenceJob{ - Spec: coh.CoherenceJobResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Coherence: &coh.CoherenceSpec{ - LocalPort: int32Ptr(port), - }, - }, - }, - } - c.Default() - g.Expect(c.Spec.Coherence).NotTo(BeNil()) - g.Expect(*c.Spec.Coherence.LocalPort).To(Equal(port)) -} - -func TestJobCoherenceLocalPortIsNotSetOnUpdate(t *testing.T) { - g := NewGomegaWithT(t) - - c := coh.CoherenceJob{} - c.Status.Phase = coh.ConditionTypeReady - c.Default() - g.Expect(c.Spec.Coherence).NotTo(BeNil()) - g.Expect(c.Spec.Coherence.LocalPort).To(BeNil()) - g.Expect(c.Spec.Coherence.LocalPortAdjust).To(BeNil()) -} - -func TestJobCoherenceLocalPortAdjustIsSet(t *testing.T) { - g := NewGomegaWithT(t) - - lpa := intstr.FromInt32(coh.DefaultUnicastPortAdjust) - c := coh.CoherenceJob{} - c.Default() - g.Expect(c.Spec.Coherence).NotTo(BeNil()) - g.Expect(*c.Spec.CoherenceResourceSpec.Coherence.LocalPortAdjust).To(Equal(lpa)) -} - -func TestJobCoherenceLocalPortAdjustIsNotOverridden(t *testing.T) { - g := NewGomegaWithT(t) - - lpa := intstr.FromInt32(9876) - c := coh.CoherenceJob{ - Spec: coh.CoherenceJobResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Coherence: &coh.CoherenceSpec{ - LocalPortAdjust: &lpa, - }, - }, - }, - } - c.Default() - g.Expect(c.Spec.Coherence).NotTo(BeNil()) - g.Expect(*c.Spec.Coherence.LocalPortAdjust).To(Equal(lpa)) -} - -func TestJobCoherenceLocalPortAdjustIsNotSetOnUpdate(t *testing.T) { - g := NewGomegaWithT(t) - - c := coh.CoherenceJob{} - c.Status.Phase = coh.ConditionTypeReady - c.Default() - g.Expect(c.Spec.Coherence).NotTo(BeNil()) - g.Expect(c.Spec.Coherence.LocalPortAdjust).To(BeNil()) -} - -func TestJobCoherenceImageIsSet(t *testing.T) { - g := NewGomegaWithT(t) - - viper.Set(operator.FlagCoherenceImage, "foo") - - c := coh.CoherenceJob{} - c.Default() - g.Expect(c.Spec.Image).NotTo(BeNil()) - g.Expect(*c.Spec.Image).To(Equal("foo")) -} - -func TestJobCoherenceImageIsNotOverriddenWhenAlreadySet(t *testing.T) { - g := NewGomegaWithT(t) - - viper.Set(operator.FlagCoherenceImage, "foo") - image := "bar" - c := coh.CoherenceJob{ - Spec: coh.CoherenceJobResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Image: &image, - }, - }, - } - - c.Default() - g.Expect(c.Spec.Image).NotTo(BeNil()) - g.Expect(*c.Spec.Image).To(Equal(image)) -} - -func TestJobUtilsImageIsSet(t *testing.T) { - g := NewGomegaWithT(t) - - viper.Set(operator.FlagOperatorImage, "foo") - - c := coh.CoherenceJob{} - c.Default() - g.Expect(c.Spec.CoherenceUtils).NotTo(BeNil()) - g.Expect(c.Spec.CoherenceUtils.Image).NotTo(BeNil()) - g.Expect(*c.Spec.CoherenceUtils.Image).To(Equal("foo")) -} - -func TestJobUtilsImageIsNotOverriddenWhenAlreadySet(t *testing.T) { - g := NewGomegaWithT(t) - - viper.Set(operator.FlagOperatorImage, "foo") - image := "bar" - c := coh.CoherenceJob{ - Spec: coh.CoherenceJobResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - CoherenceUtils: &coh.ImageSpec{ - Image: &image, - }, - }, - }, - } - - c.Default() - g.Expect(c.Spec.CoherenceUtils).NotTo(BeNil()) - g.Expect(c.Spec.CoherenceUtils.Image).NotTo(BeNil()) - g.Expect(*c.Spec.CoherenceUtils.Image).To(Equal(image)) -} - -func TestJobPersistenceModeChangeNotAllowed(t *testing.T) { - g := NewGomegaWithT(t) - - cm := "on-demand" - current := &coh.CoherenceJob{ - Spec: coh.CoherenceJobResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Coherence: &coh.CoherenceSpec{ - Persistence: &coh.PersistenceSpec{ - Mode: &cm, - PersistentStorageSpec: coh.PersistentStorageSpec{}, - Snapshots: &coh.PersistentStorageSpec{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimSpec{}, - Volume: &corev1.VolumeSource{}, - }, - }, - }, - }, - }, - } - - pm := "active" - prev := &coh.CoherenceJob{ - Spec: coh.CoherenceJobResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Coherence: &coh.CoherenceSpec{ - Persistence: &coh.PersistenceSpec{ - Mode: &pm, - PersistentStorageSpec: coh.PersistentStorageSpec{}, - Snapshots: &coh.PersistentStorageSpec{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimSpec{}, - Volume: &corev1.VolumeSource{}, - }, - }, - }, - }, - }, - } - - _, err := current.ValidateUpdate(prev) - g.Expect(err).To(HaveOccurred()) -} - -func TestJobPersistenceModeChangeAllowedIfReplicasIsZero(t *testing.T) { - g := NewGomegaWithT(t) - - cm := "on-demand" - current := &coh.CoherenceJob{ - Spec: coh.CoherenceJobResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Replicas: int32Ptr(0), - Coherence: &coh.CoherenceSpec{ - Persistence: &coh.PersistenceSpec{ - Mode: &cm, - PersistentStorageSpec: coh.PersistentStorageSpec{}, - Snapshots: &coh.PersistentStorageSpec{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimSpec{}, - Volume: &corev1.VolumeSource{}, - }, - }, - }, - }, - }, - } - - pm := "active" - prev := &coh.CoherenceJob{ - Spec: coh.CoherenceJobResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Coherence: &coh.CoherenceSpec{ - Persistence: &coh.PersistenceSpec{ - Mode: &pm, - PersistentStorageSpec: coh.PersistentStorageSpec{}, - Snapshots: &coh.PersistentStorageSpec{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimSpec{}, - Volume: &corev1.VolumeSource{}, - }, - }, - }, - }, - }, - } - - _, err := current.ValidateUpdate(prev) - g.Expect(err).NotTo(HaveOccurred()) -} - -func TestJobPersistenceModeChangeAllowedIfPreviousReplicasIsZero(t *testing.T) { - g := NewGomegaWithT(t) - - cm := "on-demand" - current := &coh.CoherenceJob{ - Spec: coh.CoherenceJobResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Coherence: &coh.CoherenceSpec{ - Persistence: &coh.PersistenceSpec{ - Mode: &cm, - PersistentStorageSpec: coh.PersistentStorageSpec{}, - Snapshots: &coh.PersistentStorageSpec{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimSpec{}, - Volume: &corev1.VolumeSource{}, - }, - }, - }, - }, - }, - } - - pm := "active" - prev := &coh.CoherenceJob{ - Spec: coh.CoherenceJobResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Replicas: int32Ptr(0), - Coherence: &coh.CoherenceSpec{ - Persistence: &coh.PersistenceSpec{ - Mode: &pm, - PersistentStorageSpec: coh.PersistentStorageSpec{}, - Snapshots: &coh.PersistentStorageSpec{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimSpec{}, - Volume: &corev1.VolumeSource{}, - }, - }, - }, - }, - }, - } - - _, err := current.ValidateUpdate(prev) - g.Expect(err).NotTo(HaveOccurred()) -} - -func TestJobPersistenceVolumeChangeNotAllowed(t *testing.T) { - g := NewGomegaWithT(t) - - cm := "on-demand" - current := &coh.CoherenceJob{ - Spec: coh.CoherenceJobResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Coherence: &coh.CoherenceSpec{ - Persistence: &coh.PersistenceSpec{ - Mode: &cm, - PersistentStorageSpec: coh.PersistentStorageSpec{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimSpec{ - VolumeName: "foo", - }, - Volume: &corev1.VolumeSource{}, - }, - Snapshots: &coh.PersistentStorageSpec{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimSpec{}, - Volume: &corev1.VolumeSource{}, - }, - }, - }, - }, - }, - } - - pm := "active" - prev := &coh.CoherenceJob{ - Spec: coh.CoherenceJobResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Coherence: &coh.CoherenceSpec{ - Persistence: &coh.PersistenceSpec{ - Mode: &pm, - PersistentStorageSpec: coh.PersistentStorageSpec{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimSpec{ - VolumeName: "bar", - }, - Volume: &corev1.VolumeSource{}, - }, - Snapshots: &coh.PersistentStorageSpec{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimSpec{}, - Volume: &corev1.VolumeSource{}, - }, - }, - }, - }, - }, - } - - _, err := current.ValidateUpdate(prev) - g.Expect(err).To(HaveOccurred()) -} - -func TestJobValidateCreateReplicasWhenReplicasIsNil(t *testing.T) { - g := NewGomegaWithT(t) - - current := &coh.CoherenceJob{ - Spec: coh.CoherenceJobResourceSpec{}, - } - - _, err := current.ValidateCreate() - g.Expect(err).NotTo(HaveOccurred()) -} - -func TestJobValidateCreateReplicasWhenReplicasIsPositive(t *testing.T) { - g := NewGomegaWithT(t) - - current := &coh.CoherenceJob{ - Spec: coh.CoherenceJobResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Replicas: ptr.To(int32(19)), - }, - }, - } - - _, err := current.ValidateCreate() - g.Expect(err).NotTo(HaveOccurred()) -} - -func TestJobValidateCreateReplicasWhenReplicasIsZero(t *testing.T) { - g := NewGomegaWithT(t) - - current := &coh.CoherenceJob{ - Spec: coh.CoherenceJobResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Replicas: ptr.To(int32(19)), - }, - }, - } - - _, err := current.ValidateCreate() - g.Expect(err).NotTo(HaveOccurred()) -} - -func TestJobValidateCreateReplicasWhenReplicasIsInvalid(t *testing.T) { - g := NewGomegaWithT(t) - - current := &coh.CoherenceJob{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: coh.CoherenceJobResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Replicas: ptr.To(int32(-1)), - }, - }, - } - - _, err := current.ValidateCreate() - g.Expect(err).To(HaveOccurred()) -} - -func TestJobValidateUpdateReplicasWhenReplicasIsNil(t *testing.T) { - g := NewGomegaWithT(t) - - current := &coh.CoherenceJob{ - Spec: coh.CoherenceJobResourceSpec{}, - } - - prev := &coh.CoherenceJob{ - Spec: coh.CoherenceJobResourceSpec{}, - } - - _, err := current.ValidateUpdate(prev) - g.Expect(err).NotTo(HaveOccurred()) -} - -func TestJobValidateUpdateReplicasWhenReplicasIsPositive(t *testing.T) { - g := NewGomegaWithT(t) - - current := &coh.CoherenceJob{ - Spec: coh.CoherenceJobResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Replicas: ptr.To(int32(9)), - }, - }, - } - - prev := &coh.CoherenceJob{ - Spec: coh.CoherenceJobResourceSpec{}, - } - - _, err := current.ValidateUpdate(prev) - g.Expect(err).NotTo(HaveOccurred()) -} - -func TestJobValidateUpdateReplicasWhenReplicasIsZero(t *testing.T) { - g := NewGomegaWithT(t) - - current := &coh.CoherenceJob{ - Spec: coh.CoherenceJobResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Replicas: ptr.To(int32(19)), - }, - }, - } - - prev := &coh.CoherenceJob{ - Spec: coh.CoherenceJobResourceSpec{}, - } - - _, err := current.ValidateUpdate(prev) - g.Expect(err).NotTo(HaveOccurred()) -} - -func TestJobValidateUpdateReplicasWhenReplicasIsInvalid(t *testing.T) { - g := NewGomegaWithT(t) - - current := &coh.CoherenceJob{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: coh.CoherenceJobResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Replicas: ptr.To(int32(-1)), - }, - }, - } - - prev := &coh.CoherenceJob{ - Spec: coh.CoherenceJobResourceSpec{}, - } - - _, err := current.ValidateUpdate(prev) - g.Expect(err).To(HaveOccurred()) -} - -func TestJobValidateVolumeClaimUpdateWhenVolumeClaimsNil(t *testing.T) { - g := NewGomegaWithT(t) - - current := &coh.CoherenceJob{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: coh.CoherenceJobResourceSpec{}, - } - - prev := &coh.CoherenceJob{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: coh.CoherenceJobResourceSpec{}, - } - - _, err := current.ValidateUpdate(prev) - g.Expect(err).NotTo(HaveOccurred()) -} - -func TestJobValidateNodePortsOnCreateWithValidPorts(t *testing.T) { - g := NewGomegaWithT(t) - - current := &coh.CoherenceJob{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: coh.CoherenceJobResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Ports: []coh.NamedPortSpec{ - { - Name: "p1", - Port: 1234, - NodePort: ptr.To(int32(30000)), - }, - { - Name: "p2", - Port: 1235, - NodePort: ptr.To(int32(32767)), - }, - }, - }, - }, - } - - _, err := current.ValidateCreate() - g.Expect(err).NotTo(HaveOccurred()) -} - -func TestJobValidateNodePortsOnCreateWithInvalidPort(t *testing.T) { - g := NewGomegaWithT(t) - - current := &coh.CoherenceJob{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: coh.CoherenceJobResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Ports: []coh.NamedPortSpec{ - { - Name: "p1", - Port: 1234, - NodePort: ptr.To(int32(30000)), - }, - { - Name: "p2", - Port: 1235, - NodePort: ptr.To(int32(32767)), - }, - { - Name: "p3", - Port: 1235, - NodePort: ptr.To(int32(1234)), - }, - }, - }, - }, - } - - _, err := current.ValidateCreate() - g.Expect(err).To(HaveOccurred()) -} - -func TestJobValidateNodePortsOnUpdateWithValidPorts(t *testing.T) { - g := NewGomegaWithT(t) - - current := &coh.CoherenceJob{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: coh.CoherenceJobResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Ports: []coh.NamedPortSpec{ - { - Name: "p1", - Port: 1234, - NodePort: ptr.To(int32(30000)), - }, - { - Name: "p2", - Port: 1235, - NodePort: ptr.To(int32(32767)), - }, - }, - }, - }, - } - - prev := &coh.CoherenceJob{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: coh.CoherenceJobResourceSpec{}, - } - - _, err := current.ValidateUpdate(prev) - g.Expect(err).NotTo(HaveOccurred()) -} - -func TestJobValidateNodePortsOnUpdateWithInvalidPort(t *testing.T) { - g := NewGomegaWithT(t) - - current := &coh.CoherenceJob{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: coh.CoherenceJobResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Ports: []coh.NamedPortSpec{ - { - Name: "p1", - Port: 1234, - NodePort: ptr.To(int32(30000)), - }, - { - Name: "p2", - Port: 1235, - NodePort: ptr.To(int32(32767)), - }, - { - Name: "p3", - Port: 1235, - NodePort: ptr.To(int32(1234)), - }, - }, - }, - }, - } - - prev := &coh.CoherenceJob{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: coh.CoherenceJobResourceSpec{}, - } - - _, err := current.ValidateUpdate(prev) - g.Expect(err).To(HaveOccurred()) -} diff --git a/api/v1/coherence_webhook_test.go b/api/v1/coherence_webhook_test.go deleted file mode 100644 index b4a9e7854..000000000 --- a/api/v1/coherence_webhook_test.go +++ /dev/null @@ -1,777 +0,0 @@ -/* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. - * Licensed under the Universal Permissive License v 1.0 as shown at - * http://oss.oracle.com/licenses/upl. - */ - -package v1_test - -import ( - . "github.com/onsi/gomega" - coh "github.com/oracle/coherence-operator/api/v1" - "github.com/oracle/coherence-operator/pkg/operator" - "github.com/spf13/viper" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/utils/ptr" - "testing" - "time" -) - -func TestDefaultReplicasIsSet(t *testing.T) { - g := NewGomegaWithT(t) - - c := coh.Coherence{} - c.Default() - g.Expect(c.Spec.CoherenceResourceSpec.Replicas).NotTo(BeNil()) - g.Expect(*c.Spec.CoherenceResourceSpec.Replicas).To(Equal(coh.DefaultReplicas)) -} - -func TestAddVersionAnnotation(t *testing.T) { - g := NewGomegaWithT(t) - - c := coh.Coherence{} - c.Default() - an := c.GetAnnotations() - g.Expect(an).NotTo(BeNil()) - g.Expect(an[coh.AnnotationOperatorVersion]).To(Equal(operator.GetVersion())) - g.Expect(*c.Spec.CoherenceResourceSpec.Replicas).To(Equal(coh.DefaultReplicas)) -} - -func TestShouldAddFinalizer(t *testing.T) { - g := NewGomegaWithT(t) - c := coh.Coherence{} - c.Default() - finalizers := c.GetFinalizers() - g.Expect(len(finalizers)).To(Equal(1)) - g.Expect(finalizers).To(ContainElement(coh.CoherenceFinalizer)) -} - -func TestShouldNotAddFinalizerAgainIfPresent(t *testing.T) { - g := NewGomegaWithT(t) - c := coh.Coherence{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Finalizers: []string{coh.CoherenceFinalizer}, - }, - } - c.Default() - finalizers := c.GetFinalizers() - g.Expect(len(finalizers)).To(Equal(1)) - g.Expect(finalizers).To(ContainElement(coh.CoherenceFinalizer)) -} - -func TestShouldNotRemoveFinalizersAlreadyPresent(t *testing.T) { - g := NewGomegaWithT(t) - c := coh.Coherence{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Finalizers: []string{"foo", "bar"}, - }, - } - c.Default() - finalizers := c.GetFinalizers() - g.Expect(len(finalizers)).To(Equal(3)) - g.Expect(finalizers).To(ContainElement("foo")) - g.Expect(finalizers).To(ContainElement("bar")) - g.Expect(finalizers).To(ContainElement(coh.CoherenceFinalizer)) -} - -func TestNoNotAddFinalizerToDeletedResource(t *testing.T) { - g := NewGomegaWithT(t) - - dt := &metav1.Time{ - Time: time.Now(), - } - - c := &coh.Coherence{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - DeletionTimestamp: dt, - }, - Spec: coh.CoherenceStatefulSetResourceSpec{}, - } - - c.Default() - finalizers := c.GetFinalizers() - g.Expect(finalizers).To(BeNil()) -} - -func TestDefaultReplicasIsNotOverriddenWhenAlreadySet(t *testing.T) { - g := NewGomegaWithT(t) - - var replicas int32 = 19 - c := coh.Coherence{ - Spec: coh.CoherenceStatefulSetResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Replicas: &replicas, - }, - }, - } - c.Default() - g.Expect(c.Spec.Replicas).NotTo(BeNil()) - g.Expect(*c.Spec.Replicas).To(Equal(replicas)) -} - -func TestCoherenceLocalPortIsSet(t *testing.T) { - g := NewGomegaWithT(t) - - c := coh.Coherence{} - c.Default() - g.Expect(c.Spec.Coherence).NotTo(BeNil()) - g.Expect(*c.Spec.Coherence.LocalPort).To(Equal(coh.DefaultUnicastPort)) -} - -func TestCoherenceLocalPortIsNotOverridden(t *testing.T) { - g := NewGomegaWithT(t) - - var port int32 = 1234 - - c := coh.Coherence{ - Spec: coh.CoherenceStatefulSetResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Coherence: &coh.CoherenceSpec{ - LocalPort: int32Ptr(port), - }, - }, - }, - } - c.Default() - g.Expect(c.Spec.Coherence).NotTo(BeNil()) - g.Expect(*c.Spec.Coherence.LocalPort).To(Equal(port)) -} - -func TestCoherenceLocalPortIsNotSetOnUpdate(t *testing.T) { - g := NewGomegaWithT(t) - - c := coh.Coherence{} - c.Status.Phase = coh.ConditionTypeReady - c.Default() - g.Expect(c.Spec.Coherence).To(BeNil()) -} - -func TestCoherenceLocalPortAdjustIsSet(t *testing.T) { - g := NewGomegaWithT(t) - - lpa := intstr.FromInt32(coh.DefaultUnicastPortAdjust) - c := coh.Coherence{} - c.Default() - g.Expect(c.Spec.Coherence).NotTo(BeNil()) - g.Expect(*c.Spec.Coherence.LocalPortAdjust).To(Equal(lpa)) -} - -func TestCoherenceLocalPortAdjustIsNotOverridden(t *testing.T) { - g := NewGomegaWithT(t) - - lpa := intstr.FromInt32(9876) - c := coh.Coherence{ - Spec: coh.CoherenceStatefulSetResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Coherence: &coh.CoherenceSpec{ - LocalPortAdjust: &lpa, - }, - }, - }, - } - c.Default() - g.Expect(c.Spec.Coherence).NotTo(BeNil()) - g.Expect(*c.Spec.Coherence.LocalPortAdjust).To(Equal(lpa)) -} - -func TestCoherenceLocalPortAdjustIsNotSetOnUpdate(t *testing.T) { - g := NewGomegaWithT(t) - - c := coh.Coherence{} - c.Status.Phase = coh.ConditionTypeReady - c.Default() - g.Expect(c.Spec.Coherence).To(BeNil()) -} - -func TestCoherenceImageIsSet(t *testing.T) { - g := NewGomegaWithT(t) - - viper.Set(operator.FlagCoherenceImage, "foo") - - c := coh.Coherence{} - c.Default() - g.Expect(c.Spec.Image).NotTo(BeNil()) - g.Expect(*c.Spec.Image).To(Equal("foo")) -} - -func TestCoherenceImageIsNotOverriddenWhenAlreadySet(t *testing.T) { - g := NewGomegaWithT(t) - - viper.Set(operator.FlagCoherenceImage, "foo") - image := "bar" - c := coh.Coherence{ - Spec: coh.CoherenceStatefulSetResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Image: &image, - }, - }, - } - - c.Default() - g.Expect(c.Spec.Image).NotTo(BeNil()) - g.Expect(*c.Spec.Image).To(Equal(image)) -} - -func TestUtilsImageIsSet(t *testing.T) { - g := NewGomegaWithT(t) - - viper.Set(operator.FlagOperatorImage, "foo") - - c := coh.Coherence{} - c.Default() - g.Expect(c.Spec.CoherenceUtils).NotTo(BeNil()) - g.Expect(c.Spec.CoherenceUtils.Image).NotTo(BeNil()) - g.Expect(*c.Spec.CoherenceUtils.Image).To(Equal("foo")) -} - -func TestUtilsImageIsNotOverriddenWhenAlreadySet(t *testing.T) { - g := NewGomegaWithT(t) - - viper.Set(operator.FlagOperatorImage, "foo") - image := "bar" - c := coh.Coherence{ - Spec: coh.CoherenceStatefulSetResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - CoherenceUtils: &coh.ImageSpec{ - Image: &image, - }, - }, - }, - } - - c.Default() - g.Expect(c.Spec.CoherenceUtils).NotTo(BeNil()) - g.Expect(c.Spec.CoherenceUtils.Image).NotTo(BeNil()) - g.Expect(*c.Spec.CoherenceUtils.Image).To(Equal(image)) -} - -func TestPersistenceModeChangeNotAllowed(t *testing.T) { - g := NewGomegaWithT(t) - - cm := "on-demand" - current := &coh.Coherence{ - Spec: coh.CoherenceStatefulSetResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Coherence: &coh.CoherenceSpec{ - Persistence: &coh.PersistenceSpec{ - Mode: &cm, - PersistentStorageSpec: coh.PersistentStorageSpec{}, - Snapshots: &coh.PersistentStorageSpec{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimSpec{}, - Volume: &corev1.VolumeSource{}, - }, - }, - }, - }, - }, - } - - pm := "active" - prev := &coh.Coherence{ - Spec: coh.CoherenceStatefulSetResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Coherence: &coh.CoherenceSpec{ - Persistence: &coh.PersistenceSpec{ - Mode: &pm, - PersistentStorageSpec: coh.PersistentStorageSpec{}, - Snapshots: &coh.PersistentStorageSpec{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimSpec{}, - Volume: &corev1.VolumeSource{}, - }, - }, - }, - }, - }, - } - - _, err := current.ValidateUpdate(prev) - g.Expect(err).To(HaveOccurred()) -} - -func TestPersistenceModeChangeAllowedIfReplicasIsZero(t *testing.T) { - g := NewGomegaWithT(t) - - cm := "on-demand" - current := &coh.Coherence{ - Spec: coh.CoherenceStatefulSetResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Replicas: int32Ptr(0), - Coherence: &coh.CoherenceSpec{ - Persistence: &coh.PersistenceSpec{ - Mode: &cm, - PersistentStorageSpec: coh.PersistentStorageSpec{}, - Snapshots: &coh.PersistentStorageSpec{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimSpec{}, - Volume: &corev1.VolumeSource{}, - }, - }, - }, - }, - }, - } - - pm := "active" - prev := &coh.Coherence{ - Spec: coh.CoherenceStatefulSetResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Coherence: &coh.CoherenceSpec{ - Persistence: &coh.PersistenceSpec{ - Mode: &pm, - PersistentStorageSpec: coh.PersistentStorageSpec{}, - Snapshots: &coh.PersistentStorageSpec{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimSpec{}, - Volume: &corev1.VolumeSource{}, - }, - }, - }, - }, - }, - } - - _, err := current.ValidateUpdate(prev) - g.Expect(err).NotTo(HaveOccurred()) -} - -func TestPersistenceModeChangeAllowedIfPreviousReplicasIsZero(t *testing.T) { - g := NewGomegaWithT(t) - - cm := "on-demand" - current := &coh.Coherence{ - Spec: coh.CoherenceStatefulSetResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Coherence: &coh.CoherenceSpec{ - Persistence: &coh.PersistenceSpec{ - Mode: &cm, - PersistentStorageSpec: coh.PersistentStorageSpec{}, - Snapshots: &coh.PersistentStorageSpec{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimSpec{}, - Volume: &corev1.VolumeSource{}, - }, - }, - }, - }, - }, - } - - pm := "active" - prev := &coh.Coherence{ - Spec: coh.CoherenceStatefulSetResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Replicas: int32Ptr(0), - Coherence: &coh.CoherenceSpec{ - Persistence: &coh.PersistenceSpec{ - Mode: &pm, - PersistentStorageSpec: coh.PersistentStorageSpec{}, - Snapshots: &coh.PersistentStorageSpec{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimSpec{}, - Volume: &corev1.VolumeSource{}, - }, - }, - }, - }, - }, - } - - _, err := current.ValidateUpdate(prev) - g.Expect(err).NotTo(HaveOccurred()) -} - -func TestPersistenceVolumeChangeNotAllowed(t *testing.T) { - g := NewGomegaWithT(t) - - cm := "on-demand" - current := &coh.Coherence{ - Spec: coh.CoherenceStatefulSetResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Coherence: &coh.CoherenceSpec{ - Persistence: &coh.PersistenceSpec{ - Mode: &cm, - PersistentStorageSpec: coh.PersistentStorageSpec{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimSpec{ - VolumeName: "foo", - }, - Volume: &corev1.VolumeSource{}, - }, - Snapshots: &coh.PersistentStorageSpec{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimSpec{}, - Volume: &corev1.VolumeSource{}, - }, - }, - }, - }, - }, - } - - pm := "active" - prev := &coh.Coherence{ - Spec: coh.CoherenceStatefulSetResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Coherence: &coh.CoherenceSpec{ - Persistence: &coh.PersistenceSpec{ - Mode: &pm, - PersistentStorageSpec: coh.PersistentStorageSpec{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimSpec{ - VolumeName: "bar", - }, - Volume: &corev1.VolumeSource{}, - }, - Snapshots: &coh.PersistentStorageSpec{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimSpec{}, - Volume: &corev1.VolumeSource{}, - }, - }, - }, - }, - }, - } - - _, err := current.ValidateUpdate(prev) - g.Expect(err).To(HaveOccurred()) -} - -func TestValidateCreateReplicasWhenReplicasIsNil(t *testing.T) { - g := NewGomegaWithT(t) - - current := &coh.Coherence{ - Spec: coh.CoherenceStatefulSetResourceSpec{}, - } - - _, err := current.ValidateCreate() - g.Expect(err).NotTo(HaveOccurred()) -} - -func TestValidateCreateReplicasWhenReplicasIsPositive(t *testing.T) { - g := NewGomegaWithT(t) - - current := &coh.Coherence{ - Spec: coh.CoherenceStatefulSetResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Replicas: ptr.To(int32(19)), - }, - }, - } - - _, err := current.ValidateCreate() - g.Expect(err).NotTo(HaveOccurred()) -} - -func TestValidateCreateReplicasWhenReplicasIsZero(t *testing.T) { - g := NewGomegaWithT(t) - - current := &coh.Coherence{ - Spec: coh.CoherenceStatefulSetResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Replicas: ptr.To(int32(19)), - }, - }, - } - - _, err := current.ValidateCreate() - g.Expect(err).NotTo(HaveOccurred()) -} - -func TestValidateCreateReplicasWhenReplicasIsInvalid(t *testing.T) { - g := NewGomegaWithT(t) - - current := &coh.Coherence{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: coh.CoherenceStatefulSetResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Replicas: ptr.To(int32(-1)), - }, - }, - } - - _, err := current.ValidateCreate() - g.Expect(err).To(HaveOccurred()) -} - -func TestValidateUpdateReplicasWhenReplicasIsNil(t *testing.T) { - g := NewGomegaWithT(t) - - current := &coh.Coherence{ - Spec: coh.CoherenceStatefulSetResourceSpec{}, - } - - prev := &coh.Coherence{ - Spec: coh.CoherenceStatefulSetResourceSpec{}, - } - - _, err := current.ValidateUpdate(prev) - g.Expect(err).NotTo(HaveOccurred()) -} - -func TestValidateUpdateReplicasWhenReplicasIsPositive(t *testing.T) { - g := NewGomegaWithT(t) - - current := &coh.Coherence{ - Spec: coh.CoherenceStatefulSetResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Replicas: ptr.To(int32(19)), - }, - }, - } - - prev := &coh.Coherence{ - Spec: coh.CoherenceStatefulSetResourceSpec{}, - } - - _, err := current.ValidateUpdate(prev) - g.Expect(err).NotTo(HaveOccurred()) -} - -func TestValidateUpdateReplicasWhenReplicasIsZero(t *testing.T) { - g := NewGomegaWithT(t) - - current := &coh.Coherence{ - Spec: coh.CoherenceStatefulSetResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Replicas: ptr.To(int32(19)), - }, - }, - } - - prev := &coh.Coherence{ - Spec: coh.CoherenceStatefulSetResourceSpec{}, - } - - _, err := current.ValidateUpdate(prev) - g.Expect(err).NotTo(HaveOccurred()) -} - -func TestValidateUpdateReplicasWhenReplicasIsInvalid(t *testing.T) { - g := NewGomegaWithT(t) - - current := &coh.Coherence{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: coh.CoherenceStatefulSetResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Replicas: ptr.To(int32(-1)), - }, - }, - } - - prev := &coh.Coherence{ - Spec: coh.CoherenceStatefulSetResourceSpec{}, - } - - _, err := current.ValidateUpdate(prev) - g.Expect(err).To(HaveOccurred()) -} - -func TestValidateVolumeClaimUpdateWhenVolumeClaimsNil(t *testing.T) { - g := NewGomegaWithT(t) - - current := &coh.Coherence{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: coh.CoherenceStatefulSetResourceSpec{}, - } - - prev := &coh.Coherence{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: coh.CoherenceStatefulSetResourceSpec{}, - } - - _, err := current.ValidateUpdate(prev) - g.Expect(err).NotTo(HaveOccurred()) -} - -func TestValidateVolumeClaimUpdateWhenVolumeClaimsNilAndEmpty(t *testing.T) { - g := NewGomegaWithT(t) - - current := &coh.Coherence{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: coh.CoherenceStatefulSetResourceSpec{ - VolumeClaimTemplates: []coh.PersistentVolumeClaim{}, - }, - } - - prev := &coh.Coherence{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: coh.CoherenceStatefulSetResourceSpec{}, - } - - _, err := current.ValidateUpdate(prev) - g.Expect(err).NotTo(HaveOccurred()) -} - -func TestValidateVolumeClaimUpdateWhenVolumeClaimsAdded(t *testing.T) { - g := NewGomegaWithT(t) - - current := &coh.Coherence{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: coh.CoherenceStatefulSetResourceSpec{ - VolumeClaimTemplates: []coh.PersistentVolumeClaim{ - { - Metadata: coh.PersistentVolumeClaimObjectMeta{Name: "foo"}, - Spec: corev1.PersistentVolumeClaimSpec{}, - }, - }, - }, - } - - prev := &coh.Coherence{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: coh.CoherenceStatefulSetResourceSpec{}, - } - - _, err := current.ValidateUpdate(prev) - g.Expect(err).To(HaveOccurred()) -} - -func TestValidateVolumeClaimUpdateWhenVolumeClaimsRemoved(t *testing.T) { - g := NewGomegaWithT(t) - - current := &coh.Coherence{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: coh.CoherenceStatefulSetResourceSpec{}, - } - - prev := &coh.Coherence{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: coh.CoherenceStatefulSetResourceSpec{ - VolumeClaimTemplates: []coh.PersistentVolumeClaim{ - { - Metadata: coh.PersistentVolumeClaimObjectMeta{Name: "foo"}, - Spec: corev1.PersistentVolumeClaimSpec{}, - }, - }, - }, - } - - _, err := current.ValidateUpdate(prev) - g.Expect(err).To(HaveOccurred()) -} - -func TestValidateNodePortsOnCreateWithValidPorts(t *testing.T) { - g := NewGomegaWithT(t) - - current := &coh.Coherence{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: coh.CoherenceStatefulSetResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Ports: []coh.NamedPortSpec{ - { - Name: "p1", - Port: 1234, - NodePort: ptr.To(int32(30000)), - }, - { - Name: "p2", - Port: 1235, - NodePort: ptr.To(int32(32767)), - }, - }, - }, - }, - } - - _, err := current.ValidateCreate() - g.Expect(err).NotTo(HaveOccurred()) -} - -func TestValidateNodePortsOnCreateWithInvalidPort(t *testing.T) { - g := NewGomegaWithT(t) - - current := &coh.Coherence{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: coh.CoherenceStatefulSetResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Ports: []coh.NamedPortSpec{ - { - Name: "p1", - Port: 1234, - NodePort: ptr.To(int32(30000)), - }, - { - Name: "p2", - Port: 1235, - NodePort: ptr.To(int32(32767)), - }, - { - Name: "p3", - Port: 1235, - NodePort: ptr.To(int32(1234)), - }, - }, - }, - }, - } - - _, err := current.ValidateCreate() - g.Expect(err).To(HaveOccurred()) -} - -func TestValidateNodePortsOnUpdateWithValidPorts(t *testing.T) { - g := NewGomegaWithT(t) - - current := &coh.Coherence{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: coh.CoherenceStatefulSetResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Ports: []coh.NamedPortSpec{ - { - Name: "p1", - Port: 1234, - NodePort: ptr.To(int32(30000)), - }, - { - Name: "p2", - Port: 1235, - NodePort: ptr.To(int32(32767)), - }, - }, - }, - }, - } - - prev := &coh.Coherence{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: coh.CoherenceStatefulSetResourceSpec{}, - } - - _, err := current.ValidateUpdate(prev) - g.Expect(err).NotTo(HaveOccurred()) -} - -func TestValidateNodePortsOnUpdateWithInvalidPort(t *testing.T) { - g := NewGomegaWithT(t) - - current := &coh.Coherence{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: coh.CoherenceStatefulSetResourceSpec{ - CoherenceResourceSpec: coh.CoherenceResourceSpec{ - Ports: []coh.NamedPortSpec{ - { - Name: "p1", - Port: 1234, - NodePort: ptr.To(int32(30000)), - }, - { - Name: "p2", - Port: 1235, - NodePort: ptr.To(int32(32767)), - }, - { - Name: "p3", - Port: 1235, - NodePort: ptr.To(int32(1234)), - }, - }, - }, - }, - } - - prev := &coh.Coherence{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: coh.CoherenceStatefulSetResourceSpec{}, - } - - _, err := current.ValidateUpdate(prev) - g.Expect(err).To(HaveOccurred()) -} diff --git a/api/v1/validate.go b/api/v1/validate.go new file mode 100644 index 000000000..fc62d1d98 --- /dev/null +++ b/api/v1/validate.go @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package v1 + +import ( + appsv1 "k8s.io/api/apps/v1" + batchv1 "k8s.io/api/batch/v1" + apiequality "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// ValidateStatefulSetUpdate tests if required fields in the StatefulSet are set. +func ValidateStatefulSetUpdate(statefulSet, oldStatefulSet *appsv1.StatefulSet) field.ErrorList { + var allErrs field.ErrorList + + // StatefulSet updates aren't super common and general updates are likely to be touching spec, so we'll do this + // deep copy right away. This avoids mutating our inputs + newStatefulSetClone := statefulSet.DeepCopy() + newStatefulSetClone.Spec.Replicas = oldStatefulSet.Spec.Replicas // +k8s:verify-mutation:reason=clone + newStatefulSetClone.Spec.Template = oldStatefulSet.Spec.Template // +k8s:verify-mutation:reason=clone + newStatefulSetClone.Spec.UpdateStrategy = oldStatefulSet.Spec.UpdateStrategy // +k8s:verify-mutation:reason=clone + newStatefulSetClone.Spec.MinReadySeconds = oldStatefulSet.Spec.MinReadySeconds // +k8s:verify-mutation:reason=clone + + newStatefulSetClone.Spec.PersistentVolumeClaimRetentionPolicy = oldStatefulSet.Spec.PersistentVolumeClaimRetentionPolicy // +k8s:verify-mutation:reason=clone + if !apiequality.Semantic.DeepEqual(newStatefulSetClone.Spec, oldStatefulSet.Spec) { + allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "updates to StatefulSet spec for fields other than 'replicas', 'template', 'updateStrategy', 'persistentVolumeClaimRetentionPolicy' and 'minReadySeconds' are forbidden")) + } + + return allErrs +} + +// ValidateJobUpdate tests if required fields in the Job are set. +func ValidateJobUpdate(job, oldJob *batchv1.Job) field.ErrorList { + var allErrs field.ErrorList + + newJobClone := job.DeepCopy() + newJobClone.Spec.ActiveDeadlineSeconds = oldJob.Spec.ActiveDeadlineSeconds // +k8s:verify-mutation:reason=clone + newJobClone.Spec.BackoffLimit = oldJob.Spec.BackoffLimit // +k8s:verify-mutation:reason=clone + newJobClone.Spec.CompletionMode = oldJob.Spec.CompletionMode // +k8s:verify-mutation:reason=clone + newJobClone.Spec.Parallelism = oldJob.Spec.Parallelism // +k8s:verify-mutation:reason=clone + newJobClone.Spec.Suspend = oldJob.Spec.Suspend // +k8s:verify-mutation:reason=clone + newJobClone.Spec.Template = oldJob.Spec.Template // +k8s:verify-mutation:reason=clone + newJobClone.Spec.TTLSecondsAfterFinished = oldJob.Spec.TTLSecondsAfterFinished // +k8s:verify-mutation:reason=clone + newJobClone.Spec.PodFailurePolicy = oldJob.Spec.PodFailurePolicy // +k8s:verify-mutation:reason=clone + newJobClone.Spec.Completions = oldJob.Spec.Completions // +k8s:verify-mutation:reason=clone + + if !apiequality.Semantic.DeepEqual(newJobClone.Spec, oldJob.Spec) { + allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "updates to Job spec for fields 'selector', 'manualSelector', are forbidden")) + } + return allErrs +} diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 177df9ee9..4d6cf33ac 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ //go:build !ignore_autogenerated /* - * Copyright (c) 2022, Oracle and/or its affiliates. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. * Licensed under the Universal Permissive License v 1.0 as shown at * http://oss.oracle.com/licenses/upl. */ @@ -998,21 +998,6 @@ func (in *CoherenceWKASpec) DeepCopy() *CoherenceWKASpec { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CommonWebHook) DeepCopyInto(out *CommonWebHook) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommonWebHook. -func (in *CommonWebHook) DeepCopy() *CommonWebHook { - if in == nil { - return nil - } - out := new(CommonWebHook) - in.DeepCopyInto(out) - return out -} - // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition. func (in *Condition) DeepCopy() *Condition { if in == nil { diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 1b75c7dab..a891693f1 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -22,7 +22,7 @@ resources: configurations: - kustomizeconfig.yaml -patchesJson6902: +patches: - path: patches/coherence.oracle.com_coherence_crd_patch.json target: group: apiextensions.k8s.io @@ -36,9 +36,12 @@ patchesJson6902: name: coherencejob.coherence.oracle.com version: v1 +labels: +- pairs: + app.kubernetes.io/component: crd + app.kubernetes.io/instance: coherence-operator-crd + app.kubernetes.io/name: coherence-operator + app.kubernetes.io/part-of: coherence-operator + commonLabels: - app.kubernetes.io/component: crd - app.kubernetes.io/instance: coherence-operator-crd - app.kubernetes.io/name: coherence-operator - app.kubernetes.io/part-of: coherence-operator app.kubernetes.io/version: 3.4.3 diff --git a/config/crd/kustomizeconfig.yaml b/config/crd/kustomizeconfig.yaml index 6f83d9a94..c1418ddee 100644 --- a/config/crd/kustomizeconfig.yaml +++ b/config/crd/kustomizeconfig.yaml @@ -1,17 +1,19 @@ # This file is for teaching kustomize how to substitute name and namespace reference in CRD nameReference: -- kind: Service - version: v1 - fieldSpecs: - - kind: CustomResourceDefinition - group: apiextensions.k8s.io - path: spec/conversion/webhookClientConfig/service/name + - kind: Service + version: v1 + fieldSpecs: + - kind: CustomResourceDefinition + version: v1 + group: apiextensions.k8s.io + path: spec/conversion/webhook/clientConfig/service/name namespace: -- kind: CustomResourceDefinition - group: apiextensions.k8s.io - path: spec/conversion/webhookClientConfig/service/namespace - create: false + - kind: CustomResourceDefinition + version: v1 + group: apiextensions.k8s.io + path: spec/conversion/webhook/clientConfig/service/namespace + create: false varReference: -- path: metadata/annotations + - path: metadata/annotations diff --git a/config/crd/patches/cainjection_in_coherence.yaml b/config/crd/patches/cainjection_in_coherence.yaml deleted file mode 100644 index 29e5715fb..000000000 --- a/config/crd/patches/cainjection_in_coherence.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# The following patch adds a directive for certmanager to inject CA into the CRD -# CRD conversion requires k8s 1.13 or later. -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) - name: coherence.coherence.oracle.com diff --git a/config/crd/patches/webhook_in_coherence.yaml b/config/crd/patches/webhook_in_coherence.yaml deleted file mode 100644 index 1a5d9f142..000000000 --- a/config/crd/patches/webhook_in_coherence.yaml +++ /dev/null @@ -1,20 +0,0 @@ -# The following patch enables conversion webhook for CRD -# CRD conversion requires k8s 1.13 or later. -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: coherence.coherence.oracle.com -spec: - conversion: - strategy: Webhook - webhook: - conversionReviewVersions: - - v1 - clientConfig: - # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, - # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) - caBundle: Cg== - service: - namespace: default - name: webhook-service - path: /convert diff --git a/config/default/config.yaml b/config/default/config.yaml deleted file mode 100644 index 1f3425fcf..000000000 --- a/config/default/config.yaml +++ /dev/null @@ -1,3 +0,0 @@ -varReference: -- path: metadata/name - kind: Namespace diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml index 3856433bb..9f8450407 100644 --- a/config/default/kustomization.yaml +++ b/config/default/kustomization.yaml @@ -8,27 +8,15 @@ namespace: default # e.g. a Deployment named "manager" becomes "coherence-operator-manager". namePrefix: "coherence-operator-" -configurations: - - config.yaml - -bases: -- ../rbac -- ../manager - -# the following config is for teaching kustomize how to do var substitution -vars: - - name: REST_SERVICE_NAME - objref: - kind: Service - version: v1 - name: rest - - name: WEBHOOK_SERVICE_NAME - objref: - kind: Service - version: v1 - name: webhook - - name: WEBHOOK_SECRET_NAME - objref: - kind: Secret - version: v1 - name: coherence-webhook-server-cert +resources: + - ../rbac + - ../manager +# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. +#- ../prometheus +# [METRICS] Expose the controller manager metrics service. + - metrics_service.yaml +# [NETWORK POLICY] Protect the /metrics endpoint and Webhook Server with NetworkPolicy. +# Only Pod(s) running a namespace labeled with 'metrics: enabled' will be able to gather the metrics. +# Only CR(s) which requires webhooks and are applied on namespaces labeled with 'webhooks: enabled' will +# be able to communicate with the Webhook Server. +#- ../network-policy diff --git a/config/default/manager_metrics_patch.yaml b/config/default/manager_metrics_patch.yaml new file mode 100644 index 000000000..2aaef6536 --- /dev/null +++ b/config/default/manager_metrics_patch.yaml @@ -0,0 +1,4 @@ +# This patch adds the args to allow exposing the metrics endpoint using HTTPS +- op: add + path: /spec/template/spec/containers/0/args/0 + value: --metrics-bind-address=:8443 diff --git a/config/default/metrics_service.yaml b/config/default/metrics_service.yaml new file mode 100644 index 000000000..243d669d1 --- /dev/null +++ b/config/default/metrics_service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + control-plane: controller-manager + app.kubernetes.io/name: coherence-operator + app.kubernetes.io/managed-by: kustomize + name: controller-manager-metrics-service + namespace: system +spec: + ports: + - name: https + port: 8443 + protocol: TCP + targetPort: 8443 + selector: + control-plane: controller-manager diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 24f912f3d..7539525f4 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -2,7 +2,6 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- webhook-secret.yaml - manager.yaml - service.yaml diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 0e97ee782..f68f572fd 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -44,9 +44,6 @@ spec: - containerPort: 8000 name: operator protocol: TCP - - containerPort: 9443 - name: webhook-server - protocol: TCP - containerPort: 8080 name: metrics protocol: TCP @@ -71,18 +68,8 @@ spec: valueFrom: fieldRef: fieldPath: metadata.name - - name: WEBHOOK_SERVICE - value: $(WEBHOOK_SERVICE_NAME) - - name: WEBHOOK_SECRET - value: $(WEBHOOK_SECRET_NAME) - name: SERVICE_NAME value: $(REST_SERVICE_NAME) - - name: CERT_TYPE - value: "self-signed" - volumeMounts: - - mountPath: /tmp/k8s-webhook-server/serving-certs - name: cert - readOnly: true readinessProbe: httpGet: port: health @@ -102,12 +89,6 @@ spec: capabilities: drop: - "ALL" - volumes: - - name: cert - secret: - defaultMode: 420 - secretName: coherence-webhook-server-cert - optional: true topologySpreadConstraints: - maxSkew: 1 topologyKey: topology.kubernetes.io/zone diff --git a/config/manager/service.yaml b/config/manager/service.yaml index a06e574ea..8b814fbb1 100644 --- a/config/manager/service.yaml +++ b/config/manager/service.yaml @@ -1,28 +1,5 @@ apiVersion: v1 kind: Service -metadata: - name: webhook - namespace: default - labels: - control-plane: coherence - app.kubernetes.io/name: coherence-operator - app.kubernetes.io/instance: coherence-operator-webhook - app.kubernetes.io/version: "3.4.3" - app.kubernetes.io/component: webhook - app.kubernetes.io/part-of: coherence-operator -spec: - ports: - - name: https-webhook - port: 443 - targetPort: webhook-server - selector: - app.kubernetes.io/name: coherence-operator - app.kubernetes.io/instance: coherence-operator-manager - app.kubernetes.io/version: "3.4.3" - app.kubernetes.io/component: manager ---- -apiVersion: v1 -kind: Service metadata: name: rest namespace: default diff --git a/config/manager/webhook-secret.yaml b/config/manager/webhook-secret.yaml deleted file mode 100644 index df6957c84..000000000 --- a/config/manager/webhook-secret.yaml +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (c) 2021, Oracle and/or its affiliates. -# Licensed under the Universal Permissive License v 1.0 as shown at -# http://oss.oracle.com/licenses/upl. -# -apiVersion: v1 -kind: Secret -metadata: - name: coherence-webhook-server-cert - namespace: coherence - labels: - control-plane: coherence diff --git a/config/manifests/kustomization.yaml b/config/manifests/kustomization.yaml index 8363fc558..2491d620e 100644 --- a/config/manifests/kustomization.yaml +++ b/config/manifests/kustomization.yaml @@ -1,7 +1,28 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization +# These resources constitute the fully configured set of manifests +# used to generate the 'manifests/' directory in a bundle. +resources: + - bases/coherence-operator.clusterserviceversion.yaml + - ../default + - ../samples + - ../scorecard -bases: -- ../default -- ../samples -- ../scorecard +# [WEBHOOK] To enable webhooks, uncomment all the sections with [WEBHOOK] prefix. +# Do NOT uncomment sections with prefix [CERTMANAGER], as OLM does not support cert-manager. +# These patches remove the unnecessary "cert" volume and its manager container volumeMount. +#patches: +#- target: +# group: apps +# version: v1 +# kind: Deployment +# name: controller-manager +# namespace: system +# patch: |- +# # Remove the manager container's "cert" volumeMount, since OLM will create and mount a set of certs. +# # Update the indices in this path if adding or removing containers/volumeMounts in the manager's Deployment. +# - op: remove + +# path: /spec/template/spec/containers/0/volumeMounts/0 +# # Remove the "cert" volume, since OLM will create and mount a set of certs. +# # Update the indices in this path if adding or removing volumes in the manager's Deployment. +# - op: remove +# path: /spec/template/spec/volumes/0 diff --git a/config/network-policy/allow-metrics-traffic.yaml b/config/network-policy/allow-metrics-traffic.yaml new file mode 100644 index 000000000..cc73c7c1a --- /dev/null +++ b/config/network-policy/allow-metrics-traffic.yaml @@ -0,0 +1,26 @@ +# This NetworkPolicy allows ingress traffic +# with Pods running on namespaces labeled with 'metrics: enabled'. Only Pods on those +# namespaces are able to gathering data from the metrics endpoint. +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + labels: + app.kubernetes.io/name: coherence-operator + app.kubernetes.io/managed-by: kustomize + name: allow-metrics-traffic + namespace: system +spec: + podSelector: + matchLabels: + control-plane: controller-manager + policyTypes: + - Ingress + ingress: + # This allows ingress traffic from any namespace with the label metrics: enabled + - from: + - namespaceSelector: + matchLabels: + metrics: enabled # Only from namespaces with this label + ports: + - port: 8443 + protocol: TCP diff --git a/config/network-policy/kustomization.yaml b/config/network-policy/kustomization.yaml new file mode 100644 index 000000000..ec0fb5e57 --- /dev/null +++ b/config/network-policy/kustomization.yaml @@ -0,0 +1,2 @@ +resources: +- allow-metrics-traffic.yaml diff --git a/config/overlays/restricted/cluster_role.yaml b/config/overlays/restricted/cluster_role.yaml deleted file mode 100644 index c8ef6b4c8..000000000 --- a/config/overlays/restricted/cluster_role.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# ------------------------------------------------------------- -# This is the Cluster Roles required by the Coherence Operator -# to self-manage its CRDs and Web-Hooks. -# ------------------------------------------------------------- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: crd-webhook-install-role -$patch: delete diff --git a/config/overlays/restricted/cluster_role_binding.yaml b/config/overlays/restricted/cluster_role_binding.yaml deleted file mode 100644 index 08c14b10b..000000000 --- a/config/overlays/restricted/cluster_role_binding.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: crd-webhook-install-rolebinding -$patch: delete diff --git a/config/overlays/restricted/kustomization.yaml b/config/overlays/restricted/kustomization.yaml index 6317684a4..10acc1d27 100644 --- a/config/overlays/restricted/kustomization.yaml +++ b/config/overlays/restricted/kustomization.yaml @@ -11,5 +11,3 @@ patches: name: controller-manager - path: node-viewer-role.yaml - path: node_viewer_role_binding.yaml - - path: cluster_role.yaml - - path: cluster_role_binding.yaml diff --git a/config/overlays/restricted/single-namespace-patch.yaml b/config/overlays/restricted/single-namespace-patch.yaml index 9d58ad9a9..6b5d7a6c6 100644 --- a/config/overlays/restricted/single-namespace-patch.yaml +++ b/config/overlays/restricted/single-namespace-patch.yaml @@ -5,9 +5,6 @@ valueFrom: fieldRef: fieldPath: metadata.namespace -- op: add - path: /spec/template/spec/containers/0/args/- - value: --enable-webhook=false - op: add path: /spec/template/spec/containers/0/args/- value: --install-crd=false diff --git a/config/prometheus/kustomization.yaml b/config/prometheus/kustomization.yaml new file mode 100644 index 000000000..ed137168a --- /dev/null +++ b/config/prometheus/kustomization.yaml @@ -0,0 +1,2 @@ +resources: +- monitor.yaml diff --git a/config/prometheus/monitor.yaml b/config/prometheus/monitor.yaml new file mode 100644 index 000000000..1ac755b91 --- /dev/null +++ b/config/prometheus/monitor.yaml @@ -0,0 +1,30 @@ +# Prometheus Monitor Service (Metrics) +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + labels: + control-plane: controller-manager + app.kubernetes.io/name: coherence-operator + app.kubernetes.io/managed-by: kustomize + name: controller-manager-metrics-monitor + namespace: system +spec: + endpoints: + - path: /metrics + port: https # Ensure this is the name of the port that exposes HTTPS metrics + scheme: https + bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token + tlsConfig: + # TODO(user): The option insecureSkipVerify: true is not recommended for production since it disables + # certificate verification. This poses a significant security risk by making the system vulnerable to + # man-in-the-middle attacks, where an attacker could intercept and manipulate the communication between + # Prometheus and the monitored services. This could lead to unauthorized access to sensitive metrics data, + # compromising the integrity and confidentiality of the information. + # Please use the following options for secure configurations: + # caFile: /etc/metrics-certs/ca.crt + # certFile: /etc/metrics-certs/tls.crt + # keyFile: /etc/metrics-certs/tls.key + insecureSkipVerify: true + selector: + matchLabels: + control-plane: controller-manager diff --git a/config/rbac/cluster_role.yaml b/config/rbac/cluster_role.yaml deleted file mode 100644 index 19f957257..000000000 --- a/config/rbac/cluster_role.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# ------------------------------------------------------------- -# This is the Cluster Roles required by the Coherence Operator -# to self-manage its CRDs and Web-Hooks. -# ------------------------------------------------------------- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: crd-webhook-install-role - labels: - control-plane: coherence -rules: - - apiGroups: - - apiextensions.k8s.io - resources: - - customresourcedefinitions - verbs: - - create - - delete - - get - - update - - apiGroups: - - admissionregistration.k8s.io - resources: - - mutatingwebhookconfigurations - - validatingwebhookconfigurations - verbs: - - create - - delete - - get - - update - - watch diff --git a/config/rbac/cluster_role_binding.yaml b/config/rbac/cluster_role_binding.yaml deleted file mode 100644 index 297e5dbd0..000000000 --- a/config/rbac/cluster_role_binding.yaml +++ /dev/null @@ -1,18 +0,0 @@ -# -------------------------------------------------------------------- -# This is the Cluster Role binding required by the Coherence Operator -# to self-manage its CRDs and Web-Hooks. -# -------------------------------------------------------------------- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: crd-webhook-install-rolebinding - labels: - control-plane: coherence -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: crd-webhook-install-role -subjects: -- kind: ServiceAccount - name: service-account - namespace: default diff --git a/config/rbac/coherence_editor_role.yaml b/config/rbac/coherence_editor_role.yaml index 90f8db744..e11d8142f 100644 --- a/config/rbac/coherence_editor_role.yaml +++ b/config/rbac/coherence_editor_role.yaml @@ -2,25 +2,26 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: coherence-editor-role labels: - control-plane: coherence + app.kubernetes.io/name: coherence-operator + app.kubernetes.io/managed-by: kustomize + name: coherence-editor-role rules: -- apiGroups: - - coherence.oracle.com - resources: - - coherence - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - coherence.oracle.com - resources: - - coherence/status - verbs: - - get + - apiGroups: + - coherence.oracle.com + resources: + - coherence + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - coherence.oracle.com + resources: + - coherence/status + verbs: + - get diff --git a/config/rbac/coherence_viewer_role.yaml b/config/rbac/coherence_viewer_role.yaml index c79edc238..af6fff2f1 100644 --- a/config/rbac/coherence_viewer_role.yaml +++ b/config/rbac/coherence_viewer_role.yaml @@ -2,21 +2,22 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: coherence-viewer-role labels: - control-plane: coherence + app.kubernetes.io/name: coherence-operator + app.kubernetes.io/managed-by: kustomize + name: coherence-viewer-role rules: -- apiGroups: - - coherence.oracle.com - resources: - - coherence - verbs: - - get - - list - - watch -- apiGroups: - - coherence.oracle.com - resources: - - coherence/status - verbs: - - get + - apiGroups: + - coherence.oracle.com + resources: + - coherence + verbs: + - get + - list + - watch + - apiGroups: + - coherence.oracle.com + resources: + - coherence/status + verbs: + - get diff --git a/config/rbac/coherencejob_editor_role.yaml b/config/rbac/coherencejob_editor_role.yaml new file mode 100644 index 000000000..5fc481b3e --- /dev/null +++ b/config/rbac/coherencejob_editor_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to edit coherencejob. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: coherence-operator + app.kubernetes.io/managed-by: kustomize + name: coherencejob-editor-role +rules: +- apiGroups: + - coherence.oracle.com + resources: + - coherencejob + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - coherence.oracle.com + resources: + - coherencejob/status + verbs: + - get diff --git a/config/rbac/coherencejob_viewer_role.yaml b/config/rbac/coherencejob_viewer_role.yaml new file mode 100644 index 000000000..079fbf478 --- /dev/null +++ b/config/rbac/coherencejob_viewer_role.yaml @@ -0,0 +1,23 @@ +# permissions for end users to view coherencejob. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: coherence-operator + app.kubernetes.io/managed-by: kustomize + name: coherencejob-viewer-role +rules: +- apiGroups: + - coherence.oracle.com + resources: + - coherencejob + verbs: + - get + - list + - watch +- apiGroups: + - coherence.oracle.com + resources: + - coherencejob/status + verbs: + - get diff --git a/config/rbac/kustomization.yaml b/config/rbac/kustomization.yaml index f3294edff..76efc18e4 100644 --- a/config/rbac/kustomization.yaml +++ b/config/rbac/kustomization.yaml @@ -1,10 +1,30 @@ resources: -- service_account.yaml -- role.yaml -- role_binding.yaml -- node_viewer_role.yaml -- node_viewer_role_binding.yaml -- cluster_role.yaml -- cluster_role_binding.yaml -- leader_election_role.yaml -- leader_election_role_binding.yaml + # All RBAC will be applied under this service account in + # the deployment namespace. You may comment out this resource + # if your manager will use a service account that exists at + # runtime. Be sure to update RoleBinding and ClusterRoleBinding + # subjects if changing service account names. + - service_account.yaml + - role.yaml + - role_binding.yaml + - node_viewer_role.yaml + - node_viewer_role_binding.yaml + - leader_election_role.yaml + - leader_election_role_binding.yaml + # The following RBAC configurations are used to protect + # the metrics endpoint with authn/authz. These configurations + # ensure that only authorized users and service accounts + # can access the metrics endpoint. Comment the following + # permissions if you want to disable this protection. + # More info: https://book.kubebuilder.io/reference/metrics.html + - metrics_auth_role.yaml + - metrics_auth_role_binding.yaml + - metrics_reader_role.yaml + # For each CRD, "Editor" and "Viewer" roles are scaffolded by + # default, aiding admins in cluster management. Those roles are + # not used by the Project itself. You can comment the following lines + # if you do not want those helpers be installed with your Project. + - coherencejob_editor_role.yaml + - coherencejob_viewer_role.yaml + - coherence_editor_role.yaml + - coherence_viewer_role.yaml diff --git a/config/rbac/leader_election_role.yaml b/config/rbac/leader_election_role.yaml index 36bfb341e..daa22601a 100644 --- a/config/rbac/leader_election_role.yaml +++ b/config/rbac/leader_election_role.yaml @@ -2,38 +2,39 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: - name: leader-election-role labels: - control-plane: coherence + app.kubernetes.io/name: coherence-operator + app.kubernetes.io/managed-by: kustomize + name: leader-election-role rules: -- apiGroups: - - "" - resources: - - configmaps - verbs: - - get - - list - - watch - - create - - update - - patch - - delete -- apiGroups: - - coordination.k8s.io - resources: - - leases - verbs: - - get - - list - - watch - - create - - update - - patch - - delete -- apiGroups: - - "" - resources: - - events - verbs: - - create - - patch + - apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch diff --git a/config/rbac/leader_election_role_binding.yaml b/config/rbac/leader_election_role_binding.yaml index b9958d300..99f0dffd5 100644 --- a/config/rbac/leader_election_role_binding.yaml +++ b/config/rbac/leader_election_role_binding.yaml @@ -1,14 +1,15 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: - name: leader-election-rolebinding labels: - control-plane: coherence + app.kubernetes.io/name: coherence-operator + app.kubernetes.io/managed-by: kustomize + name: leader-election-rolebinding roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: leader-election-role subjects: - kind: ServiceAccount - name: service-account - namespace: default + name: controller-manager + namespace: system diff --git a/config/rbac/metrics_auth_role.yaml b/config/rbac/metrics_auth_role.yaml new file mode 100644 index 000000000..32d2e4ec6 --- /dev/null +++ b/config/rbac/metrics_auth_role.yaml @@ -0,0 +1,17 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: metrics-auth-role +rules: +- apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create +- apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create diff --git a/config/rbac/metrics_auth_role_binding.yaml b/config/rbac/metrics_auth_role_binding.yaml new file mode 100644 index 000000000..e775d67ff --- /dev/null +++ b/config/rbac/metrics_auth_role_binding.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: metrics-auth-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: metrics-auth-role +subjects: +- kind: ServiceAccount + name: controller-manager + namespace: system diff --git a/config/rbac/metrics_reader_role.yaml b/config/rbac/metrics_reader_role.yaml new file mode 100644 index 000000000..51a75db47 --- /dev/null +++ b/config/rbac/metrics_reader_role.yaml @@ -0,0 +1,9 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: metrics-reader +rules: +- nonResourceURLs: + - "/metrics" + verbs: + - get diff --git a/config/rbac/node_viewer_role.yaml b/config/rbac/node_viewer_role.yaml index 2435625b0..aeb4be64a 100644 --- a/config/rbac/node_viewer_role.yaml +++ b/config/rbac/node_viewer_role.yaml @@ -7,9 +7,10 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: node-viewer-role labels: - control-plane: coherence + app.kubernetes.io/name: coherence-operator + app.kubernetes.io/managed-by: kustomize + name: node-viewer-role rules: - apiGroups: - "" diff --git a/config/rbac/node_viewer_role_binding.yaml b/config/rbac/node_viewer_role_binding.yaml index 85c70339a..5ec3cab69 100644 --- a/config/rbac/node_viewer_role_binding.yaml +++ b/config/rbac/node_viewer_role_binding.yaml @@ -9,7 +9,8 @@ kind: ClusterRoleBinding metadata: name: node-viewer-rolebinding labels: - control-plane: coherence + app.kubernetes.io/name: coherence-operator + app.kubernetes.io/managed-by: kustomize roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole diff --git a/config/rbac/role_binding.yaml b/config/rbac/role_binding.yaml index 9b70d7e81..f0d203381 100644 --- a/config/rbac/role_binding.yaml +++ b/config/rbac/role_binding.yaml @@ -5,9 +5,10 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: - name: manager-rolebinding labels: - control-plane: coherence + app.kubernetes.io/name: coherence-operator + app.kubernetes.io/managed-by: kustomize + name: manager-rolebinding roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole diff --git a/config/rbac/service_account.yaml b/config/rbac/service_account.yaml index 81444013f..2d2af29c0 100644 --- a/config/rbac/service_account.yaml +++ b/config/rbac/service_account.yaml @@ -4,6 +4,8 @@ apiVersion: v1 kind: ServiceAccount metadata: - name: service-account labels: - control-plane: coherence + app.kubernetes.io/name: coherence-operator + app.kubernetes.io/managed-by: kustomize + name: controller-manager + namespace: system diff --git a/config/samples/coherence_v1_coherencejob.yaml b/config/samples/coherence_v1_coherencejob.yaml new file mode 100644 index 000000000..3d110c7ac --- /dev/null +++ b/config/samples/coherence_v1_coherencejob.yaml @@ -0,0 +1,10 @@ +apiVersion: coherence.oracle.com/v1 +kind: CoherenceJob +metadata: + labels: + app.kubernetes.io/name: coherence-operator + app.kubernetes.io/managed-by: kustomize + name: coherencejob-sample +spec: + replicas: 1 + diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index ac0962f5c..ea51f5618 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -1,3 +1,5 @@ -## This file is auto-generated, do not modify ## +## Append samples of your project ## resources: -- coherence.oracle.com_v1_coherence.yaml + - coherence_v1_coherence.yaml + - coherence_v1_coherencejob.yaml +# +kubebuilder:scaffold:manifestskustomizesamples diff --git a/config/scorecard/kustomization.yaml b/config/scorecard/kustomization.yaml index 50cd2d084..f4ef4d647 100644 --- a/config/scorecard/kustomization.yaml +++ b/config/scorecard/kustomization.yaml @@ -1,16 +1,18 @@ resources: -- bases/config.yaml -patchesJson6902: -- path: patches/basic.config.yaml - target: - group: scorecard.operatorframework.io - version: v1alpha3 - kind: Configuration - name: config -- path: patches/olm.config.yaml - target: - group: scorecard.operatorframework.io - version: v1alpha3 - kind: Configuration - name: config -#+kubebuilder:scaffold:patchesJson6902 + - bases/config.yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +patches: + - path: patches/basic.config.yaml + target: + group: scorecard.operatorframework.io + kind: Configuration + name: config + version: v1alpha3 + - path: patches/olm.config.yaml + target: + group: scorecard.operatorframework.io + kind: Configuration + name: config + version: v1alpha3 +# +kubebuilder:scaffold:patches diff --git a/config/scorecard/patches/basic.config.yaml b/config/scorecard/patches/basic.config.yaml index d070a0e52..6ca7d78b6 100644 --- a/config/scorecard/patches/basic.config.yaml +++ b/config/scorecard/patches/basic.config.yaml @@ -2,9 +2,9 @@ path: /stages/0/tests/- value: entrypoint: - - scorecard-test - - basic-check-spec - image: quay.io/operator-framework/scorecard-test:v1.4.2 + - scorecard-test + - basic-check-spec + image: quay.io/operator-framework/scorecard-test:v1.39.1 labels: suite: basic test: basic-check-spec-test diff --git a/config/scorecard/patches/olm.config.yaml b/config/scorecard/patches/olm.config.yaml index c0dc15345..a15b6ff34 100644 --- a/config/scorecard/patches/olm.config.yaml +++ b/config/scorecard/patches/olm.config.yaml @@ -2,9 +2,9 @@ path: /stages/0/tests/- value: entrypoint: - - scorecard-test - - olm-bundle-validation - image: quay.io/operator-framework/scorecard-test:v1.4.2 + - scorecard-test + - olm-bundle-validation + image: quay.io/operator-framework/scorecard-test:v1.39.1 labels: suite: olm test: olm-bundle-validation-test @@ -12,9 +12,9 @@ path: /stages/0/tests/- value: entrypoint: - - scorecard-test - - olm-crds-have-validation - image: quay.io/operator-framework/scorecard-test:v1.4.2 + - scorecard-test + - olm-crds-have-validation + image: quay.io/operator-framework/scorecard-test:v1.39.1 labels: suite: olm test: olm-crds-have-validation-test @@ -22,9 +22,9 @@ path: /stages/0/tests/- value: entrypoint: - - scorecard-test - - olm-crds-have-resources - image: quay.io/operator-framework/scorecard-test:v1.4.2 + - scorecard-test + - olm-crds-have-resources + image: quay.io/operator-framework/scorecard-test:v1.39.1 labels: suite: olm test: olm-crds-have-resources-test @@ -32,9 +32,9 @@ path: /stages/0/tests/- value: entrypoint: - - scorecard-test - - olm-spec-descriptors - image: quay.io/operator-framework/scorecard-test:v1.4.2 + - scorecard-test + - olm-spec-descriptors + image: quay.io/operator-framework/scorecard-test:v1.39.1 labels: suite: olm test: olm-spec-descriptors-test @@ -42,9 +42,9 @@ path: /stages/0/tests/- value: entrypoint: - - scorecard-test - - olm-status-descriptors - image: quay.io/operator-framework/scorecard-test:v1.4.2 + - scorecard-test + - olm-status-descriptors + image: quay.io/operator-framework/scorecard-test:v1.39.1 labels: suite: olm test: olm-status-descriptors-test diff --git a/controllers/coherence_controller.go b/controllers/coherence_controller.go index 37ceb69ab..f91adf27a 100644 --- a/controllers/coherence_controller.go +++ b/controllers/coherence_controller.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. * Licensed under the Universal Permissive License v 1.0 as shown at * http://oss.oracle.com/licenses/upl. */ @@ -75,7 +75,6 @@ var _ reconcile.Reconciler = &CoherenceReconciler{} // +kubebuilder:rbac:groups="",resources=pods;pods/exec;services;endpoints;events;configmaps;secrets,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=apps,resources=statefulsets,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=monitoring.coreos.com,resources=servicemonitors,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=cert-manager.io,resources=certificates;issuers,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;update;patch;delete // Reconcile performs a full reconciliation for the Coherence resource referred to by the Request. @@ -108,7 +107,10 @@ func (in *CoherenceReconciler) Reconcile(ctx context.Context, request ctrl.Reque log.Info("Coherence resource not found. Ignoring request since object must be deleted.") return ctrl.Result{}, nil } - // Error reading the current deployment state from k8s. + // else... error reading the current deployment state from k8s. + msg := fmt.Sprintf("failed to find Coherence resource, %s", err.Error()) + in.GetEventRecorder().Event(deployment, coreV1.EventTypeWarning, reconciler.EventReasonFailed, msg) + // returning an error will requeue the event so we will try again ToDo: probably need som backoff here return reconcile.Result{}, errors.Wrap(err, "getting Coherence resource") } @@ -121,7 +123,10 @@ func (in *CoherenceReconciler) Reconcile(ctx context.Context, request ctrl.Reque // Run finalization logic. // If the finalization logic fails, don't remove the finalizer so // that we can retry during the next reconciliation. + in.GetEventRecorder().Event(deployment, coreV1.EventTypeNormal, reconciler.EventReasonDeleted, "running finalizers") if err := in.finalizeDeployment(ctx, deployment); err != nil { + msg := fmt.Sprintf("failed to finalize Coherence resource, %s", err.Error()) + in.GetEventRecorder().Event(deployment, coreV1.EventTypeWarning, reconciler.EventReasonDeleted, msg) log.Error(err, "Failed to remove finalizer") return ctrl.Result{Requeue: true}, nil } @@ -134,12 +139,14 @@ func (in *CoherenceReconciler) Reconcile(ctx context.Context, request ctrl.Reque log.Info("Failed to remove the finalizer from the Coherence resource, it looks like it had already been deleted") return ctrl.Result{}, nil } + msg := fmt.Sprintf("failed to remove finalizers from Coherence resource, %s", err.Error()) + in.GetEventRecorder().Event(deployment, coreV1.EventTypeWarning, reconciler.EventReasonDeleted, msg) return ctrl.Result{}, errors.Wrap(err, "trying to remove finalizer from Coherence resource") } } else { log.Info("Coherence resource deleted at " + deleteTime.String() + ", finalizer already removed") } - + // nothing else to do return ctrl.Result{}, nil } @@ -162,9 +169,16 @@ func (in *CoherenceReconciler) Reconcile(ctx context.Context, request ctrl.Reque log.Info("Finalizer not added to Coherence resource as AllowUnsafeDelete has been set to true") } } else { - // Add finalizer for this CR if required (it should have been added by the web-hook but may not have been if the - // Coherence resource was added when the Operator was uninstalled) + // Add finalizer for this CR if finalizerApplied, err := in.ensureFinalizerApplied(ctx, deployment); finalizerApplied || err != nil { + var msg string + if err != nil { + msg = fmt.Sprintf("failed to add finalizers to Coherence resource, %s", err.Error()) + in.GetEventRecorder().Event(deployment, coreV1.EventTypeWarning, reconciler.EventReasonFailed, msg) + } else { + in.GetEventRecorder().Event(deployment, coreV1.EventTypeNormal, reconciler.EventReasonUpdated, "added finalizer") + } + // we need to requeue as we have updated the Coherence resource return ctrl.Result{Requeue: true}, err } } @@ -173,6 +187,7 @@ func (in *CoherenceReconciler) Reconcile(ctx context.Context, request ctrl.Reque if deployment.Status.Phase == "" { err := in.UpdateCoherenceStatusPhase(ctx, request.NamespacedName, coh.ConditionTypeInitialized) if err != nil { + // failed to set the status return reconcile.Result{}, err } } @@ -189,9 +204,14 @@ func (in *CoherenceReconciler) Reconcile(ctx context.Context, request ctrl.Reque patch.Spec.Replicas = &replicas _, err = in.ThreeWayPatch(ctx, deployment.Name, deployment, deployment, patch) if err != nil { - log.Info("Added default replicas to Coherence resource, re-queuing request", "Replicas", strconv.Itoa(int(replicas))) - return reconcile.Result{}, err + in.GetEventRecorder().Event(deployment, coreV1.EventTypeWarning, reconciler.EventReasonFailed, + fmt.Sprintf("failed to add default replicas to Coherence resource, %s", err.Error())) + return reconcile.Result{}, errors.Wrap(err, "failed to add default replicas to Coherence resource") } + msg := "Added default replicas to Coherence resource, re-queuing request" + log.Info(msg, "Replicas", strconv.Itoa(int(replicas))) + in.GetEventRecorder().Event(deployment, coreV1.EventTypeNormal, reconciler.EventReasonUpdated, msg) + return reconcile.Result{}, err } // ensure that the Operator configuration Secret exists @@ -204,6 +224,8 @@ func (in *CoherenceReconciler) Reconcile(ctx context.Context, request ctrl.Reque storage, err := utils.NewStorage(request.NamespacedName, in.GetManager()) if err != nil { err = errors.Wrap(err, "obtaining desired state store") + in.GetEventRecorder().Event(deployment, coreV1.EventTypeWarning, reconciler.EventReasonFailed, + fmt.Sprintf("failed to obtain state store: %s", err.Error())) return in.HandleErrAndRequeue(ctx, err, nil, fmt.Sprintf(reconcileFailedMessage, request.Name, request.Namespace, err), in.Log) } @@ -211,7 +233,7 @@ func (in *CoherenceReconciler) Reconcile(ctx context.Context, request ctrl.Reque storeHash, _ := storage.GetHash() var desiredResources coh.Resources - desiredResources, err = checkCoherenceHash(deployment, storage, log) + desiredResources, err = getDesiredResources(deployment, storage, log) if err != nil { return in.HandleErrAndRequeue(ctx, err, nil, fmt.Sprintf(createResourcesFailedMessage, request.Name, request.Namespace, err), in.Log) } @@ -226,9 +248,6 @@ func (in *CoherenceReconciler) Reconcile(ctx context.Context, request ctrl.Reque return reconcile.Result{}, err } - // set the hash on all the secondary resources to match the deployment's hash - desiredResources.SetHashLabels(hash) - // update the store to have the desired state as the latest state. if err = storage.Store(desiredResources, deployment); err != nil { err = errors.Wrap(err, "storing latest state in state store") @@ -238,6 +257,7 @@ func (in *CoherenceReconciler) Reconcile(ctx context.Context, request ctrl.Reque // Ensure the version annotation is present (it should have been added by the web-hook, so this should be a no-op). // The hash may not have been added if the Coherence resource was added/modified when the Operator was uninstalled. if applied, err := in.ensureVersionAnnotationApplied(ctx, deployment); applied || err != nil { + // We updated the Coherence resource, so requeue the event return ctrl.Result{Requeue: true}, err } @@ -305,6 +325,7 @@ func (in *CoherenceReconciler) SetupWithManager(mgr ctrl.Manager, cs clients.Cli return ctrl.NewControllerManagedBy(mgr). For(template). Named("coherence"). + WithOptions(controller.Options{MaxConcurrentReconciles: 1}). Complete(in) } diff --git a/controllers/coherencejob_controller.go b/controllers/coherencejob_controller.go index 175802f91..b67b0ce8b 100644 --- a/controllers/coherencejob_controller.go +++ b/controllers/coherencejob_controller.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. * Licensed under the Universal Permissive License v 1.0 as shown at * http://oss.oracle.com/licenses/upl. */ @@ -26,6 +26,7 @@ import ( "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/reconcile" "strconv" @@ -163,7 +164,7 @@ func (in *CoherenceJobReconciler) ReconcileDeployment(ctx context.Context, reque storeHash, _ := storage.GetHash() var desiredResources coh.Resources - desiredResources, err = checkJobHash(deployment, storage, log) + desiredResources, err = getDesiredJobResources(deployment, storage, log) if err != nil { return in.HandleErrAndRequeue(ctx, err, nil, fmt.Sprintf(createResourcesFailedMessage, request.Name, request.Namespace, err), in.Log) } @@ -258,6 +259,7 @@ func (in *CoherenceJobReconciler) SetupWithManager(mgr ctrl.Manager, cs clients. return ctrl.NewControllerManagedBy(mgr). For(template). Named("coherencejob"). + WithOptions(controller.Options{MaxConcurrentReconciles: 1}). Complete(in) } diff --git a/controllers/common.go b/controllers/common.go index 1a90f240b..888ebd825 100644 --- a/controllers/common.go +++ b/controllers/common.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. * Licensed under the Universal Permissive License v 1.0 as shown at * http://oss.oracle.com/licenses/upl. */ @@ -12,11 +12,11 @@ import ( "github.com/oracle/coherence-operator/pkg/utils" ) -func checkCoherenceHash(deployment *coh.Coherence, storage utils.Storage, log logr.Logger) (coh.Resources, error) { +func getDesiredResources(deployment *coh.Coherence, storage utils.Storage, log logr.Logger) (coh.Resources, error) { return checkHash(deployment, deployment.Status.Phase, storage, log) } -func checkJobHash(deployment *coh.CoherenceJob, storage utils.Storage, log logr.Logger) (coh.Resources, error) { +func getDesiredJobResources(deployment *coh.CoherenceJob, storage utils.Storage, log logr.Logger) (coh.Resources, error) { return checkHash(deployment, deployment.Status.Phase, storage, log) } diff --git a/controllers/job/job_controller.go b/controllers/job/job_controller.go index c6b128063..5c87fd362 100644 --- a/controllers/job/job_controller.go +++ b/controllers/job/job_controller.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. * Licensed under the Universal Permissive License v 1.0 as shown at * http://oss.oracle.com/licenses/upl. */ @@ -249,7 +249,7 @@ func (in *ReconcileJob) patchJob(ctx context.Context, deployment coh.CoherenceRe errorList := coh.ValidateJobUpdate(desired, original) if len(errorList) > 0 { - msg := fmt.Sprintf("upddates to the statefuleset would have been invalid, the update will not be re-queued: %v", errorList) + msg := fmt.Sprintf("upddates to the job would have been invalid, the update will not be re-queued: %v", errorList) events := in.GetEventRecorder() events.Event(deployment, corev1.EventTypeWarning, reconciler.EventReasonUpdated, msg) return reconcile.Result{Requeue: false}, errors.New(msg) diff --git a/controllers/servicemonitor/servicemonitor_controller.go b/controllers/servicemonitor/servicemonitor_controller.go index 0c03c466e..7f3d9b14d 100644 --- a/controllers/servicemonitor/servicemonitor_controller.go +++ b/controllers/servicemonitor/servicemonitor_controller.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. * Licensed under the Universal Permissive License v 1.0 as shown at * http://oss.oracle.com/licenses/upl. */ @@ -257,14 +257,7 @@ func (in *ReconcileServiceMonitor) UpdateServiceMonitor(ctx context.Context, nam } } - if err == nil { - if hashMatches { - logger.Info("Update applied to ServiceMonitor even though hashes matched (possible external update)") - } else { - logger.Info("Update applied to ServiceMonitor") - } - } - + logger.Info("Update applied to ServiceMonitor") return nil } @@ -273,7 +266,7 @@ func (in *ReconcileServiceMonitor) hasServiceMonitor() bool { dc := discovery.NewDiscoveryClientForConfigOrDie(in.GetManager().GetConfig()) apiVersion := coh.ServiceMonitorGroupVersion kind := coh.ServiceMonitorKind - ok, err := ResourceExists(dc, apiVersion, kind) + ok, err := resourceExists(dc, apiVersion, kind) if err != nil { in.GetLog().Error(err, "error checking for Prometheus ServiceMonitor CRD") return false @@ -283,7 +276,7 @@ func (in *ReconcileServiceMonitor) hasServiceMonitor() bool { // ResourceExists returns true if the given resource kind exists // in the given api group/version -func ResourceExists(dc discovery.DiscoveryInterface, apiGroupVersion, kind string) (bool, error) { +func resourceExists(dc discovery.DiscoveryInterface, apiGroupVersion, kind string) (bool, error) { _, apiLists, err := dc.ServerGroupsAndResources() if err != nil { diff --git a/controllers/webhook/webhook.go b/controllers/webhook/webhook.go deleted file mode 100644 index b0e8167b4..000000000 --- a/controllers/webhook/webhook.go +++ /dev/null @@ -1,503 +0,0 @@ -/* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. - * Licensed under the Universal Permissive License v 1.0 as shown at - * http://oss.oracle.com/licenses/upl. - */ - -package webhook - -import ( - "context" - "fmt" - coh "github.com/oracle/coherence-operator/api/v1" - "github.com/oracle/coherence-operator/pkg/clients" - "github.com/oracle/coherence-operator/pkg/operator" - "github.com/pkg/errors" - "github.com/spf13/viper" - admissionv1 "k8s.io/api/admissionregistration/v1" - corev1 "k8s.io/api/core/v1" - kerrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" - "reflect" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "strings" -) - -type HookInstaller struct { - Clients clients.ClientSet - certManagerGroup string - certManagerAPIVersion string - issuer *unstructured.Unstructured - certificate *unstructured.Unstructured -} - -type certManagerVersion struct { - group string - versions []string -} - -const ( - admissionAPI = "admissionregistration.k8s.io/v1" - certManagerCertName = "coherence-webhook-server-certificate" - certTypeAnnotation = "operator.coherence.oracle.com/cert-type" -) - -var ( - log = logf.Log.WithName(controllerName) - - // Cert-Manager APIs that we can detect - certManagerAPIs = []certManagerVersion{ - {group: "cert-manager.io", versions: []string{"v1"}}, - } -) - -func (k *HookInstaller) uninstallWebHook() error { - log.Info("Uninstall webhook resources") - - // We only clean up cert-manager resource here. - // We specifically DO NOT clean up the web-hook resources because we do not - // want mutations of Coherence resources to go through whilst the operator is not - // running as these may result in invalid configurations. - - if k.certificate != nil { - log.Info("deleting cert-manager certificate " + k.certificate.GetName()) - if err := k.uninstallUnstructured(k.certificate); err != nil { - log.Error(err, "error deleting cert-manager Certificate "+k.certificate.GetName()) - } - } - if k.issuer != nil { - log.Info("deleting cert-manager issuer " + k.issuer.GetName()) - if err := k.uninstallUnstructured(k.issuer); err != nil { - log.Error(err, "error deleting cert-manager Issuer "+k.issuer.GetName()) - } - } - - return nil -} - -func (k *HookInstaller) InstallWithCertManager(ctx context.Context) error { - var err error - if err = k.validateCertManagerInstallation(); err != nil { - return err - } - // install the cert-manager Issuer - if err = k.installUnstructured(ctx, k.issuer); err != nil { - return err - } - // install the cert-manager Certificate - if err = k.installUnstructured(ctx, k.certificate); err != nil { - return err - } - // Install the webhooks - ns := operator.GetNamespace() - m := createMutatingWebhookWithCertManager(ns, k.certManagerGroup) - if err = installMutatingWebhook(ctx, k.Clients, m); err != nil { - return err - } - v := createValidatingWebhookWithCertManager(ns, k.certManagerGroup) - - err = installValidatingWebhook(ctx, k.Clients, v) - return err -} - -func baseWebhookSecret(ns string) *corev1.Secret { - return &corev1.Secret{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "Secret", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: viper.GetString(operator.FlagWebhookSecret), - Namespace: ns, - Labels: operator.GetGlobalLabelsNoError(), - Annotations: operator.GetGlobalAnnotationsNoError(), - }, - Type: "kubernetes.io/tls", - } -} - -func (k *HookInstaller) detectCertManagerVersion() error { - for _, api := range certManagerAPIs { - group, version, err := k.detectCertManagerCRD(api) - if err != nil { - return err - } - if group != "" && version != "" { - log.Info(fmt.Sprintf("Detected cert-manager CRDs %s/%s", k.certManagerGroup, k.certManagerAPIVersion)) - - if !contains(api.versions, version) { - return errors.Wrap(err, fmt.Sprintf("Detected cert-manager CRDs with version %s, only versions %v are fully supported. Certificates for webhooks may not work.", version, api.versions)) - } - - k.certManagerGroup = group - k.certManagerAPIVersion = version - - log.Info(fmt.Sprintf("Detected cert-manager %s/%s", group, version)) - return nil - } - } - return fmt.Errorf("failed to detect any valid cert-manager CRDs. Make sure cert-manager is installed") -} - -func (k *HookInstaller) detectCertManagerCRD(api certManagerVersion) (string, string, error) { - testCRD := fmt.Sprintf("certificates.%s", api.group) - log.Info(fmt.Sprintf("Try to retrieve cert-manager CRD %s", testCRD)) - crd, err := k.Clients.ExtClient.ApiextensionsV1().CustomResourceDefinitions().Get(context.TODO(), testCRD, metav1.GetOptions{}) - if err == nil { - // crd.Spec.Versions[0] must be the one that is stored and served, we should use that one - log.Info(fmt.Sprintf("Got CRD. Group: %s, Version: %s", api.group, crd.Spec.Versions[0].Name)) - return api.group, crd.Spec.Versions[0].Name, nil - } - if !kerrors.IsNotFound(err) { - return "", "", fmt.Errorf("failed to detect cert manager CRD %s: %v", testCRD, err) - } - return "", "", nil -} - -func (k *HookInstaller) validateCertManagerInstallation() error { - if err := k.detectCertManagerVersion(); err != nil { - return err - } - - certificateCRD := fmt.Sprintf("certificates.%s", k.certManagerGroup) - if err := k.validateCrdVersion(certificateCRD, k.certManagerAPIVersion); err != nil { - return err - } - issuerCRD := fmt.Sprintf("issuers.%s", k.certManagerGroup) - if err := k.validateCrdVersion(issuerCRD, k.certManagerAPIVersion); err != nil { - return err - } - - // Initialize the custom resources that we're going to install - k.certificate = certificate(operator.GetNamespace(), k.certManagerGroup, k.certManagerAPIVersion) - k.issuer = issuer(operator.GetNamespace(), k.certManagerGroup, k.certManagerAPIVersion) - - return nil -} - -func (k *HookInstaller) validateCrdVersion(crdName string, expectedVersion string) error { - certCRD, err := k.Clients.ExtClient.ApiextensionsV1().CustomResourceDefinitions().Get(context.TODO(), crdName, metav1.GetOptions{}) - if err != nil { - if kerrors.IsNotFound(err) { - return errors.Wrap(err, fmt.Sprintf("failed to find CRD '%s': %s", crdName, err)) - } - return err - } - crdVersion := certCRD.Spec.Versions[0].Name - - if crdVersion != expectedVersion { - return errors.Wrap(err, fmt.Sprintf("invalid CRD version found for '%s': %s instead of %s", crdName, crdVersion, expectedVersion)) - } - log.Info(fmt.Sprintf("CRD %s is installed with version %s", crdName, crdVersion)) - return nil -} - -func (k *HookInstaller) installUnstructured(ctx context.Context, item *unstructured.Unstructured) error { - var err error - - gvk := item.GroupVersionKind() - gvr := schema.GroupVersionResource{ - Group: gvk.Group, - Version: gvk.Version, - Resource: fmt.Sprintf("%ss", strings.ToLower(gvk.Kind)), // since we know what kinds are we dealing with here, this is OK - } - - client := k.Clients.DynamicClient.Resource(gvr).Namespace(item.GetNamespace()) - - _, err = client.Get(ctx, item.GetName(), metav1.GetOptions{}) - if err != nil { - // issuer does not exist, so create it - _, err = client.Create(context.TODO(), item, metav1.CreateOptions{}) - if kerrors.IsAlreadyExists(err) { - log.Info(fmt.Sprintf("resource %s/%s %s already registered and will not be overwritten", item.GetAPIVersion(), item.GetKind(), item.GetName())) - } else if err != nil { - return fmt.Errorf("error when creating resource %s/%s. %v", item.GetName(), item.GetNamespace(), err) - } - log.Info(fmt.Sprintf("created resource %s/%s %s", item.GetAPIVersion(), item.GetKind(), item.GetName())) - } else { - log.Info(fmt.Sprintf("resource %s/%s %s already registered and will not be overwritten", item.GetAPIVersion(), item.GetKind(), item.GetName())) - } - - return nil -} - -func (k *HookInstaller) uninstallUnstructured(item *unstructured.Unstructured) error { - gvk := item.GroupVersionKind() - err := k.Clients.DynamicClient.Resource(schema.GroupVersionResource{ - Group: gvk.Group, - Version: gvk.Version, - Resource: fmt.Sprintf("%ss", strings.ToLower(gvk.Kind)), // since we know what kinds are we dealing with here, this is OK - }).Namespace(item.GetNamespace()).Delete(context.TODO(), item.GetName(), metav1.DeleteOptions{}) - if err != nil { - return fmt.Errorf("error when deleting resource %s/%s. %v", item.GetName(), item.GetNamespace(), err) - } - return nil -} - -func installMutatingWebhook(ctx context.Context, c clients.ClientSet, webhook admissionv1.MutatingWebhookConfiguration) error { - log.Info(fmt.Sprintf("installing webhook %s/%s", webhook.Namespace, webhook.Name)) - cl := c.KubeClient.AdmissionregistrationV1() - existing, err := cl.MutatingWebhookConfigurations().Get(context.TODO(), webhook.GetName(), metav1.GetOptions{}) - exists := err == nil - - if exists && existing != nil { - existing.Webhooks = webhook.Webhooks - existing.Annotations = webhook.Annotations - _, err = cl.MutatingWebhookConfigurations().Update(ctx, existing, metav1.UpdateOptions{}) - } else { - _, err = cl.MutatingWebhookConfigurations().Create(ctx, &webhook, metav1.CreateOptions{}) - } - return err -} - -func installValidatingWebhook(ctx context.Context, c clients.ClientSet, webhook admissionv1.ValidatingWebhookConfiguration) error { - log.Info(fmt.Sprintf("installing webhook %s/%s", webhook.Namespace, webhook.Name)) - cl := c.KubeClient.AdmissionregistrationV1() - existing, err := cl.ValidatingWebhookConfigurations().Get(context.TODO(), webhook.GetName(), metav1.GetOptions{}) - exists := err == nil - - if exists && existing != nil { - existing.Webhooks = webhook.Webhooks - existing.Annotations = webhook.Annotations - _, err = cl.ValidatingWebhookConfigurations().Update(ctx, existing, metav1.UpdateOptions{}) - } else { - _, err = cl.ValidatingWebhookConfigurations().Create(ctx, &webhook, metav1.CreateOptions{}) - } - return err -} - -func createMutatingWebhookWithCABundle(ns string, caData []byte) admissionv1.MutatingWebhookConfiguration { - cfg := createMutatingWebhookConfiguration(ns) - for i := range cfg.Webhooks { - cfg.Webhooks[i].ClientConfig.CABundle = caData - } - return cfg -} - -func createValidatingWebhookWithCABundle(ns string, caData []byte) admissionv1.ValidatingWebhookConfiguration { - cfg := createValidatingWebhookConfiguration(ns) - for i := range cfg.Webhooks { - cfg.Webhooks[i].ClientConfig.CABundle = caData - } - return cfg -} - -func createMutatingWebhookWithCertManager(ns string, certManagerGroup string) admissionv1.MutatingWebhookConfiguration { - cfg := createMutatingWebhookConfiguration(ns) - injectCaAnnotationName := fmt.Sprintf("%s/inject-ca-from", certManagerGroup) - cfg.Annotations[injectCaAnnotationName] = fmt.Sprintf("%s/%s", ns, certManagerCertName) - return cfg -} - -func createValidatingWebhookWithCertManager(ns string, certManagerGroup string) admissionv1.ValidatingWebhookConfiguration { - cfg := createValidatingWebhookConfiguration(ns) - injectCaAnnotationName := fmt.Sprintf("%s/inject-ca-from", certManagerGroup) - cfg.Annotations[injectCaAnnotationName] = fmt.Sprintf("%s/%s", ns, certManagerCertName) - return cfg -} - -func createMutatingWebhookConfiguration(ns string) admissionv1.MutatingWebhookConfiguration { - namespacedScope := admissionv1.NamespacedScope - failedType := admissionv1.Fail - equivalentType := admissionv1.Equivalent - noSideEffects := admissionv1.SideEffectClassNone - path := coh.MutatingWebHookPath - clientConfig := createWebhookClientConfig(ns, path) - labels := operator.GetGlobalLabelsNoError() - ann := operator.GetGlobalAnnotationsNoError() - if ann == nil { - ann = make(map[string]string) - } - ann[certTypeAnnotation] = viper.GetString(operator.FlagCertType) - - return admissionv1.MutatingWebhookConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: viper.GetString(operator.FlagMutatingWebhookName), - Labels: labels, - Annotations: ann, - }, - TypeMeta: metav1.TypeMeta{ - Kind: "MutatingWebhookConfiguration", - APIVersion: admissionAPI, - }, - Webhooks: []admissionv1.MutatingWebhook{ - { - Name: "coherence.oracle.com", - AdmissionReviewVersions: []string{"v1", "v1beta1"}, - Rules: []admissionv1.RuleWithOperations{ - { - Operations: []admissionv1.OperationType{"CREATE", "UPDATE"}, - Rule: admissionv1.Rule{ - APIGroups: []string{"coherence.oracle.com"}, - APIVersions: []string{"v1"}, - Resources: []string{"coherence"}, - Scope: &namespacedScope, - }, - }, - }, - FailurePolicy: &failedType, // this means that the request to update instance would fail, if webhook is not up - MatchPolicy: &equivalentType, - SideEffects: &noSideEffects, - ClientConfig: clientConfig, - }, - }, - } -} - -func createValidatingWebhookConfiguration(ns string) admissionv1.ValidatingWebhookConfiguration { - namespacedScope := admissionv1.NamespacedScope - failedType := admissionv1.Fail - equivalentType := admissionv1.Equivalent - noSideEffects := admissionv1.SideEffectClassNone - path := coh.ValidatingWebHookPath - clientConfig := createWebhookClientConfig(ns, path) - labels := operator.GetGlobalLabelsNoError() - ann := operator.GetGlobalAnnotationsNoError() - if ann == nil { - ann = make(map[string]string) - } - ann[certTypeAnnotation] = viper.GetString(operator.FlagCertType) - - return admissionv1.ValidatingWebhookConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: viper.GetString(operator.FlagValidatingWebhookName), - Labels: labels, - Annotations: ann, - }, - TypeMeta: metav1.TypeMeta{ - Kind: "ValidatingWebhookConfiguration", - APIVersion: admissionAPI, - }, - Webhooks: []admissionv1.ValidatingWebhook{ - { - Name: "coherence.oracle.com", - AdmissionReviewVersions: []string{"v1", "v1beta1"}, - Rules: []admissionv1.RuleWithOperations{ - { - Operations: []admissionv1.OperationType{"CREATE", "UPDATE"}, - Rule: admissionv1.Rule{ - APIGroups: []string{"coherence.oracle.com"}, - APIVersions: []string{"v1"}, - Resources: []string{"coherence"}, - Scope: &namespacedScope, - }, - }, - }, - FailurePolicy: &failedType, // this means that the request to update instance would fail, if webhook is not up - MatchPolicy: &equivalentType, - SideEffects: &noSideEffects, - ClientConfig: clientConfig, - }, - }, - } -} - -func createWebhookClientConfig(ns, path string) admissionv1.WebhookClientConfig { - - var clientConfig admissionv1.WebhookClientConfig - if operator.IsDevMode() { - hn := operator.GetWebhookServiceDNSNames()[0] - url := fmt.Sprintf("https://%s:9443%s", hn, path) - clientConfig = admissionv1.WebhookClientConfig{ - URL: &url, - } - } else { - clientConfig = admissionv1.WebhookClientConfig{ - Service: &admissionv1.ServiceReference{ - Name: viper.GetString(operator.FlagWebhookService), - Namespace: ns, - Path: &path, - }, - } - } - return clientConfig -} - -func issuer(ns string, group string, apiVersion string) *unstructured.Unstructured { - apiString := fmt.Sprintf("%s/%s", group, apiVersion) - certIssuer := viper.GetString(operator.FlagCertIssuer) - labels := operator.GetGlobalLabelsNoError() - ann := operator.GetGlobalAnnotationsNoError() - return &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": apiString, - "kind": "Issuer", - "metadata": map[string]interface{}{ - "name": certIssuer, - "namespace": ns, - "labels": labels, - "annotations": ann, - }, - "spec": map[string]interface{}{ - "selfSigned": map[string]interface{}{}, - }, - }, - } -} - -func certificate(ns string, group string, apiVersion string) *unstructured.Unstructured { - apiString := fmt.Sprintf("%s/%s", group, apiVersion) - name := viper.GetString(operator.FlagWebhookService) - certIssuer := viper.GetString(operator.FlagCertIssuer) - dns := operator.GetWebhookServiceDNSNames() - labels := operator.GetGlobalLabelsNoError() - ann := operator.GetGlobalAnnotationsNoError() - return &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": apiString, - "kind": "Certificate", - "metadata": map[string]interface{}{ - "name": certManagerCertName, - "namespace": ns, - "labels": labels, - "annotations": ann, - }, - "spec": map[string]interface{}{ - "commonName": fmt.Sprintf("%s.%s.svc", name, ns), - "dnsNames": dns, - "issuerRef": map[string]interface{}{ - "kind": "Issuer", - "name": certIssuer, - }, - "secretName": viper.GetString(operator.FlagWebhookSecret), - }, - }, - } -} - -// Contains returns true if an element is present in a iteratee. -func contains(in interface{}, elem interface{}) bool { - inValue := reflect.ValueOf(in) - elemValue := reflect.ValueOf(elem) - inType := inValue.Type() - - switch inType.Kind() { - case reflect.String: - return strings.Contains(inValue.String(), elemValue.String()) - case reflect.Map: - for _, key := range inValue.MapKeys() { - if equal(key.Interface(), elem) { - return true - } - } - case reflect.Slice, reflect.Array: - for i := 0; i < inValue.Len(); i++ { - if equal(inValue.Index(i).Interface(), elem) { - return true - } - } - default: - panic(fmt.Sprintf("Type %s is not supported by Contains, supported types are String, Map, Slice, Array", inType.String())) - } - - return false -} - -func equal(expected, actual interface{}) bool { - if expected == nil || actual == nil { - return expected == actual - } - return reflect.DeepEqual(expected, actual) -} diff --git a/controllers/webhook/webhook_controller.go b/controllers/webhook/webhook_controller.go deleted file mode 100644 index 59af9738f..000000000 --- a/controllers/webhook/webhook_controller.go +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. - * Licensed under the Universal Permissive License v 1.0 as shown at - * http://oss.oracle.com/licenses/upl. - */ - -package webhook - -import ( - "bytes" - "context" - "fmt" - "github.com/oracle/coherence-operator/controllers/predicates" - "github.com/oracle/coherence-operator/controllers/reconciler" - "github.com/oracle/coherence-operator/pkg/certs" - "github.com/oracle/coherence-operator/pkg/clients" - "github.com/oracle/coherence-operator/pkg/operator" - "github.com/pkg/errors" - "github.com/spf13/viper" - corev1 "k8s.io/api/core/v1" - kerrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "os" - "path/filepath" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - "time" -) - -const ( - // The name of this controller. This is used in events, log messages, etc. - controllerName = "controllers.Certs" -) - -// blank assignment to verify that CertReconciler implements reconcile.Reconciler. -// If the "reconcile.Reconciler" API was to change then we'd get a compile error here. -var _ reconcile.Reconciler = &CertReconciler{} - -type CertReconciler struct { - reconciler.CommonReconciler - Clientset clients.ClientSet - rotateBefore time.Duration - hookInstaller *HookInstaller -} - -func (r *CertReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, cs clients.ClientSet) error { - r.SetCommonReconciler(controllerName, mgr, cs) - r.rotateBefore = operator.GetCACertRotateBefore() - - // determine how webhook certs will be managed - switch { - case operator.ShouldUseCertManager(): - r.hookInstaller = &HookInstaller{Clients: r.Clientset} - if err := r.hookInstaller.InstallWithCertManager(ctx); err != nil { - return errors.Wrap(err, " unable to install cert-manager resources") - } - // if in dev-mode write the certs to local cert files - if err := r.writeLocalCerts(ctx); err != nil { - return err - } - case operator.ShouldUseSelfSignedCerts(): - // do an initial reconcile to make sure certs and web hooks are configured - if err := r.ReconcileResources(ctx); err != nil { - return errors.Wrap(err, " unable to setup and fill the webhook certificates") - } - default: - // certificates are manually managed - if err := r.writeLocalCerts(ctx); err != nil { - return err - } - // don't use this controller for manual certs so just return - return nil - } - - // set-up this controller - return ctrl.NewControllerManagedBy(mgr). - For(&corev1.Secret{}). - Named("coherence-webhook-server-cert"). - WithEventFilter(&predicates.NamedPredicate{ - Namespace: operator.GetNamespace(), - Name: viper.GetString(operator.FlagWebhookSecret), - }). - Complete(r) -} - -func (r *CertReconciler) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { - if err := r.ReconcileResources(ctx); err != nil { - return reconcile.Result{}, err - } - - namespace := operator.GetNamespace() - secretName := request.Name - secret, err := r.Clientset.KubeClient.CoreV1().Secrets(namespace).Get(ctx, secretName, metav1.GetOptions{}) - if err != nil { - return reconcile.Result{}, err - } - - if operator.ShouldUseCertManager() { - // for cert-manager certs we don't need to do anything except update the local certs - if operator.IsDevMode() { - if err := r.writeLocalCertsFromSecret(secret); err != nil { - r.GetLog().Error(err, "error writing local certs") - } - } - return reconcile.Result{}, nil - } - - serverCA := certs.BuildCAFromSecret(*secret) - if serverCA == nil { - return reconcile.Result{}, fmt.Errorf("cannot find CA in webhook secret %s/%s", request.Namespace, secretName) - } - - return reconcile.Result{ - RequeueAfter: certs.RotateIn(time.Now(), serverCA.Cert.NotAfter, r.rotateBefore), - }, nil -} - -// ReconcileResources reconciles the certificates used by the webhook client and the webhook server. -// It also returns the duration after which a certificate rotation should be scheduled. -func (r *CertReconciler) ReconcileResources(ctx context.Context) error { - var err error - secretName := operator.GetViper().GetString(operator.FlagWebhookSecret) - namespace := operator.GetNamespace() - updateSecret := true - - secClient := r.Clientset.KubeClient.CoreV1().Secrets(namespace) - secret, err := secClient.Get(ctx, secretName, metav1.GetOptions{}) - - if err != nil { - if operator.IsDevMode() && kerrors.IsNotFound(err) { - // if in dev mode and we're using self-signed certs we can deal with the secret not being there - secret = baseWebhookSecret(namespace) - updateSecret = false - } else { - return errors.Wrap(err, fmt.Sprintf("failed to get webhook certificate secret %s", secretName)) - } - } - - serverCA := certs.BuildCAFromSecret(*secret) - - // check if we need to renew the certificates used in the resources - if serverCA.ShouldRenew(r.rotateBefore) { - r.GetLog().Info("Creating new mutating webhook certificates", - "secret_namespace", secret.Namespace, - "secret_name", secret.Name, - ) - - ca, err := certs.CreateSelfSignedCA() - if err != nil { - return errors.Wrap(err, "unable to set up webhook CA") - } - // update the cert secret - ca.PopulateSecret(secret) - - if updateSecret { - if _, err := secClient.Update(ctx, secret, metav1.UpdateOptions{}); err != nil { - return err - } - } else { - if _, err := secClient.Create(ctx, secret, metav1.CreateOptions{}); err != nil { - return err - } - } - // refresh the local copy of the certs - serverCA = certs.BuildCAFromSecret(*secret) - } - - if operator.IsDevMode() { - // In dev mode (i.e. outside k8s) write the certs to the local cert dir - if err = r.writeLocalCertsFromSecret(secret); err != nil { - return err - } - } - - if r.shouldRenewWebhookConfigs(ctx, serverCA) { - m := createMutatingWebhookWithCABundle(operator.GetNamespace(), secret.Data[operator.CertFileName]) - if err = installMutatingWebhook(ctx, r.Clientset, m); err != nil { - return err - } - v := createValidatingWebhookWithCABundle(operator.GetNamespace(), secret.Data[operator.CertFileName]) - if err = installValidatingWebhook(ctx, r.Clientset, v); err != nil { - return err - } - } - - return nil -} - -func (r *CertReconciler) writeLocalCerts(ctx context.Context) error { - if !operator.IsDevMode() { - return nil - } - secretName := viper.GetString(operator.FlagWebhookSecret) - namespace := operator.GetNamespace() - secret, err := r.Clientset.KubeClient.CoreV1().Secrets(namespace).Get(ctx, secretName, metav1.GetOptions{}) - if err != nil { - return errors.Wrap(err, fmt.Sprintf("failed to get secret %s", secretName)) - } - return r.writeLocalCertsFromSecret(secret) -} - -func (r *CertReconciler) writeLocalCertsFromSecret(secret *corev1.Secret) error { - certDir := operator.GetWebhookCertDir() - _, err := os.Stat(certDir) - if err != nil { - if err = os.MkdirAll(certDir, os.ModePerm); err != nil { - return errors.Wrap(err, "creating local cert directory") - } - } - - for name, data := range secret.Data { - fn := filepath.Join(certDir, name) - if err = os.WriteFile(fn, data, os.ModePerm); err != nil { - return errors.Wrap(err, "writing local cert file") - } - } - return nil -} - -func (r *CertReconciler) shouldRenewWebhookConfigs(ctx context.Context, ca *certs.CA) bool { - // Read the current certificate used by the server - mCfg, err := r.Clientset.KubeClient.AdmissionregistrationV1(). - MutatingWebhookConfigurations().Get(ctx, viper.GetString(operator.FlagMutatingWebhookName), metav1.GetOptions{}) - if err != nil { - // probably does not exist, so needs creating - return true - } - - expectedType := viper.GetString(operator.FlagCertType) - certType, found := mCfg.Annotations[certTypeAnnotation] - if !found || certType != expectedType { - return true - } - - vCfg, err := r.Clientset.KubeClient.AdmissionregistrationV1(). - ValidatingWebhookConfigurations().Get(ctx, viper.GetString(operator.FlagValidatingWebhookName), metav1.GetOptions{}) - if err != nil { - // probably does not exist, so needs creating - return true - } - - certType, found = vCfg.Annotations[certTypeAnnotation] - if !found || certType != expectedType { - return true - } - - // Read the certificate in the mutating webhook configuration - for _, webhook := range mCfg.Webhooks { - caBytes := webhook.ClientConfig.CABundle - if len(caBytes) == 0 || !bytes.Equal(caBytes, ca.Cert.Raw) { - return true - } - } - - // Read the certificate in the mutating webhook configuration - for _, webhook := range vCfg.Webhooks { - caBytes := webhook.ClientConfig.CABundle - if len(caBytes) == 0 || !bytes.Equal(caBytes, ca.Cert.Raw) { - return true - } - } - - return false -} - -func (r *CertReconciler) Cleanup() { - r.GetLog().Info("cleaning up") - if r.hookInstaller != nil { - if err := r.hookInstaller.uninstallWebHook(); err != nil { - r.GetLog().Error(err, "error cleaning up") - } - } -} diff --git a/go.mod b/go.mod index 12c328493..ccfa8b973 100644 --- a/go.mod +++ b/go.mod @@ -5,25 +5,25 @@ go 1.23.0 toolchain go1.23.4 require ( - github.com/distribution/reference v0.6.0 github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 github.com/go-logr/logr v1.4.2 github.com/go-test/deep v1.1.1 + github.com/onsi/ginkgo/v2 v2.22.2 github.com/onsi/gomega v1.36.2 github.com/pkg/errors v0.9.1 - github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.79.2 - github.com/prometheus-operator/prometheus-operator/pkg/client v0.79.2 - github.com/spf13/cobra v1.8.1 - github.com/spf13/pflag v1.0.5 + github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.80.1 + github.com/prometheus-operator/prometheus-operator/pkg/client v0.80.1 + github.com/spf13/cobra v1.9.1 + github.com/spf13/pflag v1.0.6 github.com/spf13/viper v1.19.0 - golang.org/x/mod v0.22.0 - golang.org/x/net v0.33.0 - k8s.io/api v0.32.0 - k8s.io/apiextensions-apiserver v0.32.0 - k8s.io/apimachinery v0.32.0 - k8s.io/client-go v0.32.0 + golang.org/x/mod v0.23.0 + golang.org/x/net v0.35.0 + k8s.io/api v0.32.2 + k8s.io/apiextensions-apiserver v0.32.2 + k8s.io/apimachinery v0.32.2 + k8s.io/client-go v0.32.2 k8s.io/utils v0.0.0-20241210054802-24370beab758 - sigs.k8s.io/controller-runtime v0.19.3 + sigs.k8s.io/controller-runtime v0.20.2 sigs.k8s.io/testing_frameworks v0.1.2 ) @@ -31,64 +31,69 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.12.1 // indirect - github.com/evanphx/json-patch v5.9.0+incompatible // indirect - github.com/evanphx/json-patch/v5 v5.9.0 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/emicklei/go-restful/v3 v3.12.2 // indirect + github.com/evanphx/json-patch/v5 v5.9.11 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-logr/zapr v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect + github.com/google/btree v1.1.3 // indirect github.com/google/gnostic-models v0.6.9 // indirect - github.com/google/go-cmp v0.6.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect github.com/google/gofuzz v1.2.0 // indirect + github.com/google/pprof v0.0.0-20250208200701-d0013a598941 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/websocket v1.5.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/magiconair/properties v1.8.7 // indirect - github.com/mailru/easyjson v0.7.7 // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/magiconair/properties v1.8.9 // indirect + github.com/mailru/easyjson v0.9.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moby/spdystream v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/pelletier/go-toml/v2 v2.2.2 // indirect - github.com/prometheus/client_golang v1.19.1 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/prometheus/client_golang v1.21.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect - github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect + github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.11.0 // indirect - github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/afero v1.12.0 // indirect + github.com/spf13/cast v1.7.1 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/oauth2 v0.24.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/term v0.27.0 // indirect - golang.org/x/text v0.21.0 // indirect - golang.org/x/time v0.8.0 // indirect + golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa // indirect + golang.org/x/oauth2 v0.27.0 // indirect + golang.org/x/sync v0.11.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/term v0.29.0 // indirect + golang.org/x/text v0.22.0 // indirect + golang.org/x/time v0.10.0 // indirect + golang.org/x/tools v0.30.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/protobuf v1.36.1 // indirect + google.golang.org/protobuf v1.36.5 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20241212045625-5ad02ce6640f // indirect + k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.5.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect diff --git a/go.sum b/go.sum index 25323de97..006f07bd9 100644 --- a/go.sum +++ b/go.sum @@ -4,23 +4,21 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= -github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= -github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= -github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= -github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= +github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k= +github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= +github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= +github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew= @@ -44,20 +42,22 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69 github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= -github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20250208200701-d0013a598941 h1:43XjGa6toxLpeksjcxs1jIoIyr+vUfOqY2c6HB4bpoc= +github.com/google/pprof v0.0.0-20250208200701-d0013a598941/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -68,6 +68,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -75,10 +77,12 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= +github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= @@ -94,61 +98,53 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/onsi/ginkgo v1.4.0 h1:n60/4GZK0Sr9O2iuGKq876Aoa0ER2ydgpMOBwzJ8e2c= github.com/onsi/ginkgo v1.4.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo/v2 v2.22.1 h1:QW7tbJAUDyVDVOM5dFa7qaybo+CRfR7bemlQUN6Z8aM= -github.com/onsi/ginkgo/v2 v2.22.1/go.mod h1:S6aTpoRsSq2cZOd+pssHAlKW/Q/jZt6cPrPlnj4a1xM= +github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= +github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= github.com/onsi/gomega v1.3.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= -github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.79.2 h1:DGv150w4UyxnjNHlkCw85R3+lspOxegtdnbpP2vKRrk= -github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.79.2/go.mod h1:AVMP4QEW8xuGWnxaWSpI3kKjP9fDA31nO68zsyREJZA= -github.com/prometheus-operator/prometheus-operator/pkg/client v0.79.2 h1:wUMuHTC069Ayy+0/srqD5OrLVP/QRhSCUR/7SJ8tSqQ= -github.com/prometheus-operator/prometheus-operator/pkg/client v0.79.2/go.mod h1:671/KciyzKiTmvIYTpp7CzWD1/TNXVPgeDLJcGFWrOM= -github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= -github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.80.1 h1:DP+PUNVOc+Bkft8a4QunLzaZ0RspWuD3tBbcPHr2PeE= +github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.80.1/go.mod h1:6x4x0t9BP35g4XcjkHE9EB3RxhyfxpdpmZKd/Qyk8+M= +github.com/prometheus-operator/prometheus-operator/pkg/client v0.80.1 h1:7/TPlTy7tkMXC1KwS2WdUdU8J220DszlSQ1+KHjXwHc= +github.com/prometheus-operator/prometheus-operator/pkg/client v0.80.1/go.mod h1:0lW4yp2XE7AKohpGB2UGdomjP08rbr4f8JwAN6sUDWU= +github.com/prometheus/client_golang v1.21.0 h1:DIsaGmiaBkSangBgMtWdNfxbMNdku5IK6iNhrEqWvdA= +github.com/prometheus/client_golang v1.21.0/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= -github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= -github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= +github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= -github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= @@ -164,53 +160,55 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4= +golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180112015858-5ccada7d0a7b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= -golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180117170059-2c42eef0765b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20171227012246-e19ae1496984/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= -golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= +golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= -golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= -google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= @@ -225,25 +223,24 @@ gopkg.in/yaml.v2 v2.0.0/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.32.0 h1:OL9JpbvAU5ny9ga2fb24X8H6xQlVp+aJMFlgtQjR9CE= -k8s.io/api v0.32.0/go.mod h1:4LEwHZEf6Q/cG96F3dqR965sYOfmPM7rq81BLgsE0p0= -k8s.io/apiextensions-apiserver v0.32.0 h1:S0Xlqt51qzzqjKPxfgX1xh4HBZE+p8KKBq+k2SWNOE0= -k8s.io/apiextensions-apiserver v0.32.0/go.mod h1:86hblMvN5yxMvZrZFX2OhIHAuFIMJIZ19bTvzkP+Fmw= -k8s.io/apimachinery v0.32.0 h1:cFSE7N3rmEEtv4ei5X6DaJPHHX0C+upp+v5lVPiEwpg= -k8s.io/apimachinery v0.32.0/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= -k8s.io/client-go v0.32.0 h1:DimtMcnN/JIKZcrSrstiwvvZvLjG0aSxy8PxN8IChp8= -k8s.io/client-go v0.32.0/go.mod h1:boDWvdM1Drk4NJj/VddSLnx59X3OPgwrOo0vGbtq9+8= +k8s.io/api v0.32.2 h1:bZrMLEkgizC24G9eViHGOPbW+aRo9duEISRIJKfdJuw= +k8s.io/api v0.32.2/go.mod h1:hKlhk4x1sJyYnHENsrdCWw31FEmCijNGPJO5WzHiJ6Y= +k8s.io/apiextensions-apiserver v0.32.2 h1:2YMk285jWMk2188V2AERy5yDwBYrjgWYggscghPCvV4= +k8s.io/apiextensions-apiserver v0.32.2/go.mod h1:GPwf8sph7YlJT3H6aKUWtd0E+oyShk/YHWQHf/OOgCA= +k8s.io/apimachinery v0.32.2 h1:yoQBR9ZGkA6Rgmhbp/yuT9/g+4lxtsGYwW6dR6BDPLQ= +k8s.io/apimachinery v0.32.2/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/client-go v0.32.2 h1:4dYCD4Nz+9RApM2b/3BtVvBHw54QjMFUl1OLcJG5yOA= +k8s.io/client-go v0.32.2/go.mod h1:fpZ4oJXclZ3r2nDOv+Ux3XcJutfrwjKTCHz2H3sww94= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20241212045625-5ad02ce6640f h1:jyijczch0N1N0Uk0p4P0hfgTP+2Rkt4m6pVX8l3R6q4= -k8s.io/kube-openapi v0.0.0-20241212045625-5ad02ce6640f/go.mod h1:iZjdMQzunI7O/sUrf/5WRX1gvaAIam32lKx9+paoLbU= +k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 h1:hcha5B1kVACrLujCKLbr8XWMxCxzQx42DY8QKYJrDLg= +k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7/go.mod h1:GewRfANuJ70iYzvn+i4lezLDAFzvjxZYK1gn1lWcfas= k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/controller-runtime v0.19.3 h1:XO2GvC9OPftRst6xWCpTgBZO04S2cbp0Qqkj8bX1sPw= -sigs.k8s.io/controller-runtime v0.19.3/go.mod h1:j4j87DqtsThvwTv5/Tc5NFRyyF/RF0ip4+62tbTSIUM= +sigs.k8s.io/controller-runtime v0.20.2 h1:/439OZVxoEc02psi1h4QO3bHzTgu49bb347Xp4gW1pc= +sigs.k8s.io/controller-runtime v0.20.2/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/structured-merge-diff/v4 v4.5.0 h1:nbCitCK2hfnhyiKo6uf2HxUPTCodY6Qaf85SbDIaMBk= diff --git a/helm-charts/coherence-operator/templates/deployment.yaml b/helm-charts/coherence-operator/templates/deployment.yaml index 04dec6ff4..5f34e469c 100644 --- a/helm-charts/coherence-operator/templates/deployment.yaml +++ b/helm-charts/coherence-operator/templates/deployment.yaml @@ -1,50 +1,3 @@ -{{- if ne .Values.webhookCertType "manual" }} ---- -apiVersion: v1 -kind: Secret -metadata: - name: {{ default "coherence-webhook-server-cert" .Values.webhookCertSecret }} - namespace: {{ .Release.Namespace }} -{{- if (.Values.globalLabels) }} - labels: -{{ toYaml .Values.globalLabels | indent 4 }} -{{- end }} -{{- if (.Values.globalAnnotations) }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} -{{- end }} -{{- end }} ---- -apiVersion: v1 -kind: Service -metadata: - name: coherence-operator-webhook - namespace: {{ .Release.Namespace }} - labels: - control-plane: coherence - app.kubernetes.io/name: coherence-operator - app.kubernetes.io/instance: coherence-operator-manager - app.kubernetes.io/version: "${VERSION}" - app.kubernetes.io/component: webhook - app.kubernetes.io/part-of: coherence-operator - app.kubernetes.io/managed-by: helm -{{- if (.Values.globalLabels) }} -{{ toYaml .Values.globalLabels | indent 4 }} -{{- end }} -{{- if (.Values.globalAnnotations) }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} -{{- end }} -spec: - ports: - - name: webhook - port: 443 - targetPort: 9443 - selector: - app.kubernetes.io/name: coherence-operator - app.kubernetes.io/instance: coherence-operator-manager - app.kubernetes.io/version: "${VERSION}" - app.kubernetes.io/component: manager --- apiVersion: v1 kind: Service @@ -152,12 +105,7 @@ spec: - --node-lookup-enabled=false {{- end }} {{- if (eq .Values.clusterRoles false) }} - - --enable-webhook=false - --install-crd=false -{{- else }} -{{- if (eq .Values.webhooks false) }} - - --enable-webhook=false -{{- end }} {{- end }} {{- if (eq .Values.allowCoherenceJobs false) }} - --install-job-crd=false @@ -183,14 +131,8 @@ spec: valueFrom: fieldRef: fieldPath: metadata.name - - name: WEBHOOK_SERVICE - value: coherence-operator-webhook - - name: WEBHOOK_SECRET - value: {{ default "coherence-webhook-server-cert" .Values.webhookCertSecret }} - name: SERVICE_NAME value: coherence-operator-rest - - name: CERT_TYPE - value: {{ default "self-signed" .Values.webhookCertType | quote }} - name: COHERENCE_IMAGE {{- if kindIs "string" .Values.defaultCoherenceImage }} value: {{ .Values.defaultCoherenceImage | quote }} @@ -226,19 +168,12 @@ spec: - containerPort: 8000 name: operator protocol: TCP - - name: webhook-server - containerPort: 9443 - protocol: TCP - containerPort: 8080 name: metrics protocol: TCP - containerPort: 8088 name: health protocol: TCP - volumeMounts: - - mountPath: /tmp/k8s-webhook-server/serving-certs - name: cert - readOnly: true readinessProbe: httpGet: port: health @@ -337,8 +272,3 @@ spec: app.kubernetes.io/version: "${VERSION}" weight: 1 {{- end }} - volumes: - - name: cert - secret: - defaultMode: 420 - secretName: {{ .Values.webhookCertSecret }} diff --git a/helm-charts/coherence-operator/templates/rbac.yaml b/helm-charts/coherence-operator/templates/rbac.yaml index 16a3d3707..e01086ec7 100644 --- a/helm-charts/coherence-operator/templates/rbac.yaml +++ b/helm-charts/coherence-operator/templates/rbac.yaml @@ -238,19 +238,6 @@ rules: - patch - update - watch -- apiGroups: - - cert-manager.io - resources: - - certificates - - issuers - verbs: - - create - - delete - - get - - list - - patch - - update - - watch --- # --------------------------------------------------------------------- # This is the Cluster Role binding required by the Coherence Operator diff --git a/helm-charts/coherence-operator/values.yaml b/helm-charts/coherence-operator/values.yaml index 6c159a117..f5b11f476 100644 --- a/helm-charts/coherence-operator/values.yaml +++ b/helm-charts/coherence-operator/values.yaml @@ -118,18 +118,6 @@ nodeSelector: # ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ tolerations: -# webhookCertType sets how webhook certificates will be managed. -# Valid values are "self-signed" "cert-manager" or "manual" the Operator will fail to start if -# the value is invalid. -# The default is "self-signed" where the operator will create self-signed certs for the webhook. -# If set to "cert-manager" it is expected that cert-manager is installed and configured to -# manage the certificates. https://cert-manager.io/docs/installation/kubernetes/ -# If set to "manual" the certificate Secret should be created and managed externally -webhookCertType: self-signed - -# webhookCertSecret is the name of the Secret that will contain the certificates for the Coherence webhooks. -webhookCertSecret: coherence-webhook-server-cert - # siteLabel is the Kubernetes Node label used to set the site identity for Coherence Pods. siteLabel: @@ -205,11 +193,6 @@ clusterRoles: true # to set theCoherence site and rack values so Coherence cluster will be unable to automatically achieve site-safety. # The default is true. nodeRoles: false -# webhooks controls whether the Coherence Operator registers admission web-hooks for the Coherence resource. -# If this is set to false, then it will be possible to install invalid Coherence resource into the Kubernetes -# cluster, that may cause errors when the Operator tries to reconcile them, or worse the Operator may create -# other invalid Kubernetes resources that fail to run. -webhooks: true # If set to false, the Operator will not support the CoherenceJob resource type. # The CoherenceJob CRD will not be installed by the Operator and the Operator will diff --git a/pkg/certs/certs.go b/pkg/certs/certs.go deleted file mode 100644 index 9014191dc..000000000 --- a/pkg/certs/certs.go +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. - * Licensed under the Universal Permissive License v 1.0 as shown at - * http://oss.oracle.com/licenses/upl. - */ - -package certs - -import ( - "bytes" - cryptorand "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" - "github.com/oracle/coherence-operator/pkg/operator" - "github.com/pkg/errors" - "github.com/spf13/viper" - corev1 "k8s.io/api/core/v1" - "math/big" - ctrl "sigs.k8s.io/controller-runtime" - "strings" - "time" -) - -var ( - log = ctrl.Log.WithName("certificates") - - // SerialNumberLimit is the maximum number used as a certificate serial number - SerialNumberLimit = new(big.Int).Lsh(big.NewInt(1), 128) -) - -// CA is a simple certificate authority -type CA struct { - // PrivateKey is the CA private key - PrivateKey *rsa.PrivateKey - // Cert is the certificate used to issue new certificates - Cert *x509.Certificate -} - -func (c *CA) PopulateSecret(secret *corev1.Secret) { - secret.Data = map[string][]byte{ - operator.CertFileName: encodePEMCert(c.Cert.Raw), - operator.KeyFileName: encodePEMPrivateKey(c.PrivateKey), - } -} - -func (c *CA) ShouldRenew(rotateBefore time.Duration) bool { - // Read the current certificate used by the server - if c == nil { - return true - } - // check whether the certs have expired - if !CanReuseCA(c, rotateBefore) { - return true - } - // check the DNS names - if !c.hasCorrectDNS() { - return true - } - return false -} - -func (c *CA) hasCorrectDNS() bool { - dns := operator.GetWebhookServiceDNSNames() - for _, name := range c.Cert.DNSNames { - if strings.HasPrefix(name, dns[0]) { - return true - } - } - return false -} - -// WebhookCertificates holds the artifacts used by the webhook server and the webhook configuration. -type WebhookCertificates struct { - CaCert []byte - ServerKey []byte - ServerCert []byte -} - -func CreateSelfSignedCA() (*CA, error) { - // generate a serial number - serial, err := cryptorand.Int(cryptorand.Reader, SerialNumberLimit) - if err != nil { - return nil, err - } - - privateKey, err := rsa.GenerateKey(cryptorand.Reader, 2048) - if err != nil { - return nil, errors.Wrap(err, "unable to generate the private key") - } - - expireIn := viper.GetDuration(operator.FlagCACertValidity) - notAfter := time.Now().Add(expireIn) - - subject := pkix.Name{ - CommonName: "coherence-webhook-ca", - OrganizationalUnit: []string{"coherence-webhook"}, - } - - certTemplate := x509.Certificate{ - SerialNumber: serial, - Subject: subject, - NotBefore: time.Now().Add(-10 * time.Minute), - NotAfter: notAfter, - SignatureAlgorithm: x509.SHA256WithRSA, - IsCA: true, - BasicConstraintsValid: true, - KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, - DNSNames: operator.GetWebhookServiceDNSNames(), - } - - certData, err := x509.CreateCertificate(cryptorand.Reader, &certTemplate, &certTemplate, privateKey.Public(), privateKey) - if err != nil { - return nil, err - } - - cert, err := x509.ParseCertificate(certData) - if err != nil { - return nil, err - } - - return &CA{PrivateKey: privateKey, Cert: cert}, nil -} - -// BuildCAFromSecret parses the given secret into a CA. -// It returns nil if the secrets could not be parsed into a CA. -func BuildCAFromSecret(caInternalSecret corev1.Secret) *CA { - if caInternalSecret.Data == nil { - return nil - } - caBytes, exists := caInternalSecret.Data[operator.CertFileName] - if !exists || len(caBytes) == 0 { - return nil - } - certs, err := parsePEMCerts(caBytes) - if err != nil { - log.Error(err, "Cannot parse PEM cert from CA secret, will create a new one", "namespace", caInternalSecret.Namespace, "secret_name", caInternalSecret.Name) - return nil - } - if len(certs) == 0 { - return nil - } - if len(certs) > 1 { - log.Info( - "More than 1 certificate in the CA secret, continuing with the first one", - "namespace", caInternalSecret.Namespace, - "secret_name", caInternalSecret.Name, - ) - } - cert := certs[0] - - privateKeyBytes, exists := caInternalSecret.Data[operator.KeyFileName] - if !exists || len(privateKeyBytes) == 0 { - return nil - } - privateKey, err := parsePEMPrivateKey(privateKeyBytes) - if err != nil { - log.Error(err, "Cannot parse PEM private key from CA secret, will create a new one", "namespace", caInternalSecret.Namespace, "secret_name", caInternalSecret.Name) - return nil - } - - return &CA{ - PrivateKey: privateKey, - Cert: cert, - } -} - -// encodePEMCert encodes the given certificate blocks as a PEM certificate -func encodePEMCert(certBlocks ...[]byte) []byte { - var buf bytes.Buffer - for _, block := range certBlocks { - _, _ = buf.Write(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: block})) - } - return buf.Bytes() -} - -// encodePEMPrivateKey encodes the given private key in the PEM format -func encodePEMPrivateKey(privateKey *rsa.PrivateKey) []byte { - return pem.EncodeToMemory(&pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(privateKey), - }) -} - -func parsePEMCerts(pemData []byte) ([]*x509.Certificate, error) { - certs := []*x509.Certificate{} - for len(pemData) > 0 { - var block *pem.Block - block, pemData = pem.Decode(pemData) - if block == nil { - break - } - if block.Type != "CERTIFICATE" || len(block.Headers) != 0 { - continue - } - - cert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - return nil, errors.WithStack(err) - } - - certs = append(certs, cert) - } - return certs, nil -} - -func parsePEMPrivateKey(pemData []byte) (*rsa.PrivateKey, error) { - block, _ := pem.Decode(pemData) - if block == nil { - return nil, errors.New("failed to parse PEM block containing private key") - } - - switch { - case block.Type == "PRIVATE KEY": - return parsePKCS8PrivateKey(block.Bytes) - case block.Type == "RSA PRIVATE KEY" && len(block.Headers) == 0: - return x509.ParsePKCS1PrivateKey(block.Bytes) - default: - return nil, errors.New("expected PEM block to contain an RSA private key") - } -} - -func parsePKCS8PrivateKey(block []byte) (*rsa.PrivateKey, error) { - key, err := x509.ParsePKCS8PrivateKey(block) - if err != nil { - return nil, errors.Wrap(err, "failed to parse private key") - } - - rsaKey, ok := key.(*rsa.PrivateKey) - if !ok { - return nil, errors.Errorf("expected an RSA private key but got %t", key) - } - - return rsaKey, nil -} - -// RotateIn determines when a cert should be rotated -func RotateIn(now time.Time, certExpiration time.Time, certRotateBefore time.Duration) time.Duration { - // make sure we are past the safety margin when rotating, by making it a little bit shorter - safetyMargin := certRotateBefore - 1*time.Second - requeueTime := certExpiration.Add(-safetyMargin) - requeueIn := requeueTime.Sub(now) - if requeueIn < 0 { - // requeue asap - requeueIn = 0 - } - return requeueIn -} - -func CanReuseCA(ca *CA, expirationSafetyMargin time.Duration) bool { - return privateMatchesPublicKey(ca.Cert.PublicKey, *ca.PrivateKey) && CertIsValid(*ca.Cert, expirationSafetyMargin) -} - -func CertIsValid(cert x509.Certificate, expirationSafetyMargin time.Duration) bool { - now := time.Now() - if now.Before(cert.NotBefore) { - log.Info("CA cert is not valid yet", "subject", cert.Subject) - return false - } - if now.After(cert.NotAfter.Add(-expirationSafetyMargin)) { - log.Info("CA cert expired or soon to expire", "subject", cert.Subject, "expiration", cert.NotAfter) - return false - } - return true -} - -func privateMatchesPublicKey(publicKey interface{}, privateKey rsa.PrivateKey) bool { - pubKey, ok := publicKey.(*rsa.PublicKey) - if !ok { - log.Error(errors.New("Public key is not an RSA public key"), "") - return false - } - // check that public and private keys share the same modulus and exponent - if pubKey.N.Cmp(privateKey.N) != 0 || pubKey.E != privateKey.E { - return false - } - return true -} diff --git a/pkg/operator/operator.go b/pkg/operator/operator.go index 3b693074b..31f39473c 100644 --- a/pkg/operator/operator.go +++ b/pkg/operator/operator.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. * Licensed under the Universal Permissive License v 1.0 as shown at * http://oss.oracle.com/licenses/upl. */ @@ -18,67 +18,37 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/version" "os" - "path/filepath" ctrl "sigs.k8s.io/controller-runtime" "strings" - "time" ) const ( DefaultRestHost = "0.0.0.0" DefaultRestPort int32 = 8000 - // DefaultCertValidity makes new certificates default to a one-year expiration - DefaultCertValidity = 365 * 24 * time.Hour - // DefaultRotateBefore defines how long before expiration a certificate - // should be re-issued - DefaultRotateBefore = 24 * time.Hour - - // CertFileName is used for Certificates inside a secret - CertFileName = "tls.crt" - // KeyFileName is used for Private Keys inside a secret - KeyFileName = "tls.key" - - CertTypeSelfSigned = "self-signed" - CertTypeCertManager = "cert-manager" - CertTypeManual = "manual" - CertManagerIssuerName = "coherence-webhook-server-issuer" - - DefaultMutatingWebhookName = "coherence-operator-mutating-webhook-configuration" - DefaultValidatingWebhookName = "coherence-operator-validating-webhook-configuration" - - FlagCACertRotateBefore = "ca-cert-rotate-before" - FlagCACertValidity = "ca-cert-validity" - FlagCertType = "cert-type" - FlagCertIssuer = "cert-issuer" - FlagCoherenceImage = "coherence-image" - FlagCRD = "install-crd" - FlagJobCRD = "install-job-crd" - FlagDevMode = "coherence-dev-mode" - FlagDryRun = "dry-run" - FlagEnableWebhook = "enable-webhook" - FlagGlobalAnnotation = "global-annotation" - FlagGlobalLabel = "global-label" - FlagHealthAddress = "health-addr" - FlagLeaderElection = "enable-leader-election" - FlagMetricsAddress = "metrics-addr" - FlagMutatingWebhookName = "mutating-webhook-name" - FlagOperatorNamespace = "operator-namespace" - FlagNodeLookupEnabled = "node-lookup-enabled" - FlagRackLabel = "rack-label" - FlagRestHost = "rest-host" - FlagRestPort = "rest-port" - FlagServiceName = "service-name" - FlagServicePort = "service-port" - FlagSiteLabel = "site-label" - FlagSkipServiceSuspend = "skip-service-suspend" - FlagOperatorImage = "operator-image" - FlagValidatingWebhookName = "validating-webhook-name" - FlagWebhookCertDir = "webhook-cert-dir" - FlagWebhookSecret = "webhook-secret" - FlagWebhookService = "webhook-service" - FlagEnvVar = "env" - FlagJvmArg = "jvm" + FlagCoherenceImage = "coherence-image" + FlagCRD = "install-crd" + FlagJobCRD = "install-job-crd" + FlagDevMode = "coherence-dev-mode" + FlagDryRun = "dry-run" + FlagEnableWebhook = "enable-webhook" + FlagGlobalAnnotation = "global-annotation" + FlagGlobalLabel = "global-label" + FlagHealthAddress = "health-addr" + FlagLeaderElection = "enable-leader-election" + FlagMetricsAddress = "metrics-addr" + FlagOperatorNamespace = "operator-namespace" + FlagNodeLookupEnabled = "node-lookup-enabled" + FlagRackLabel = "rack-label" + FlagRestHost = "rest-host" + FlagRestPort = "rest-port" + FlagServiceName = "service-name" + FlagServicePort = "service-port" + FlagSiteLabel = "site-label" + FlagSkipServiceSuspend = "skip-service-suspend" + FlagOperatorImage = "operator-image" + FlagEnvVar = "env" + FlagJvmArg = "jvm" // EnvVarWatchNamespace is the environment variable to use to set the watch namespace(s) EnvVarWatchNamespace = "WATCH_NAMESPACE" @@ -140,28 +110,6 @@ func SetupFlags(cmd *cobra.Command, v *viper.Viper) { } _ = f.Close() - cmd.Flags().Duration( - FlagCACertRotateBefore, - DefaultRotateBefore, - "Duration representing how long before expiration CA certificates should be reissued", - ) - cmd.Flags().Duration( - FlagCACertValidity, - DefaultCertValidity, - "Duration representing how long before a newly created CA cert expires", - ) - cmd.Flags().String( - FlagCertType, - CertTypeSelfSigned, - fmt.Sprintf("The type of certificate management used for webhook certificates. "+ - "Valid options are %v", []string{CertTypeSelfSigned, CertTypeCertManager, CertTypeManual}), - ) - cmd.Flags().String( - FlagCertIssuer, - CertManagerIssuerName, - fmt.Sprintf("The name of an existing cert-manager issuer (in the same namespace) used for webhook certificates. "+ - "Valid options are %v", []string{CertTypeSelfSigned, CertTypeCertManager, CertTypeManual}), - ) cmd.Flags().String( FlagCoherenceImage, "", @@ -184,13 +132,8 @@ func SetupFlags(cmd *cobra.Command, v *viper.Viper) { ) cmd.Flags().Bool( FlagEnableWebhook, - true, - "Enables the defaulting and validating web-hooks", - ) - cmd.Flags().String( - FlagMutatingWebhookName, - DefaultMutatingWebhookName, - "Name of the Kubernetes ValidatingWebhookConfiguration resource. Only used when enable-webhook is true.", + false, + "This flag is here for backward compatibility but is ignored", ) cmd.Flags().Bool( FlagNodeLookupEnabled, @@ -244,26 +187,6 @@ func SetupFlags(cmd *cobra.Command, v *viper.Viper) { "", "The default Coherence Operator image to use if none is specified.", ) - cmd.Flags().String( - FlagValidatingWebhookName, - DefaultValidatingWebhookName, - "Name of the Kubernetes ValidatingWebhookConfiguration resource. Only used when enable-webhook is true.", - ) - cmd.Flags().String( - FlagWebhookCertDir, - filepath.Join(os.TempDir(), "k8s-webhook-server", "serving-certs"), - "The name of the directory containing the webhook server key and certificate", - ) - cmd.Flags().String( - FlagWebhookSecret, - "coherence-webhook-server-cert", - "K8s secret to be used for webhook certificates", - ) - cmd.Flags().String( - FlagWebhookService, - "webhook-service", - "The K8s service used for the webhook", - ) cmd.Flags().StringArray( FlagGlobalAnnotation, nil, @@ -284,32 +207,6 @@ func SetupFlags(cmd *cobra.Command, v *viper.Viper) { v.AutomaticEnv() } -func ValidateFlags(v *viper.Viper) error { - var err error - certValidity := v.GetDuration(FlagCACertValidity) - certRotateBefore := v.GetDuration(FlagCACertRotateBefore) - if certRotateBefore > certValidity { - return fmt.Errorf("%s must be larger than %s", FlagCACertValidity, FlagCACertRotateBefore) - } - - certType := v.GetString(FlagCertType) - if certType != CertTypeSelfSigned && certType != CertTypeCertManager && certType != CertTypeManual { - return fmt.Errorf("%s parameter is invalid", FlagCertType) - } - - _, err = GetGlobalAnnotations(v) - if err != nil { - return err - } - - _, err = GetGlobalLabels(v) - if err != nil { - return err - } - - return err -} - func SetViper(v *viper.Viper) { currentViper = v } @@ -321,10 +218,6 @@ func GetViper() *viper.Viper { return currentViper } -func IsDevMode() bool { - return GetViper().GetBool(FlagDevMode) -} - func GetDefaultCoherenceImage() string { return GetViper().GetString(FlagCoherenceImage) } @@ -369,54 +262,18 @@ func ShouldInstallJobCRD() bool { return GetViper().GetBool(FlagJobCRD) } -func ShouldEnableWebhooks() bool { - return GetViper().GetBool(FlagEnableWebhook) && !IsDryRun() -} - func IsDryRun() bool { return GetViper().GetBool(FlagDryRun) } -func ShouldUseSelfSignedCerts() bool { - return GetViper().GetString(FlagCertType) == CertTypeSelfSigned -} - -func ShouldUseCertManager() bool { - return GetViper().GetString(FlagCertType) == CertTypeCertManager -} - func GetNamespace() string { return GetViper().GetString(FlagOperatorNamespace) } -func GetWebhookCertDir() string { - return GetViper().GetString(FlagWebhookCertDir) -} - -func GetCACertRotateBefore() time.Duration { - return GetViper().GetDuration(FlagCACertRotateBefore) -} - func IsNodeLookupEnabled() bool { return GetViper().GetBool(FlagNodeLookupEnabled) } -func GetWebhookServiceDNSNames() []string { - var dns []string - s := GetViper().GetString(FlagWebhookService) - if IsDevMode() { - dns = []string{s} - } else { - ns := GetNamespace() - return []string{ - fmt.Sprintf("%s.%s", s, ns), - fmt.Sprintf("%s.%s.svc", s, ns), - fmt.Sprintf("%s.%s.svc.cluster.local", s, ns), - } - } - return dns -} - func DetectKubernetesVersion(cs clients.ClientSet) (*version.Version, error) { sv, err := cs.DiscoveryClient.ServerVersion() if err != nil { diff --git a/pkg/runner/cmd_operator.go b/pkg/runner/cmd_operator.go index 1b7759314..00a2e9db1 100644 --- a/pkg/runner/cmd_operator.go +++ b/pkg/runner/cmd_operator.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. * Licensed under the Universal Permissive License v 1.0 as shown at * http://oss.oracle.com/licenses/upl. */ @@ -11,7 +11,6 @@ import ( "fmt" coh "github.com/oracle/coherence-operator/api/v1" "github.com/oracle/coherence-operator/controllers" - "github.com/oracle/coherence-operator/controllers/webhook" "github.com/oracle/coherence-operator/pkg/clients" "github.com/oracle/coherence-operator/pkg/operator" "github.com/oracle/coherence-operator/pkg/rest" @@ -177,25 +176,6 @@ func execute() error { // We intercept the signal handler here so that we can do clean-up before the Manager stops handler := ctrl.SetupSignalHandler() - // Set-up webhooks if required - var cr *webhook.CertReconciler - if operator.ShouldEnableWebhooks() { - // Set up the webhook certificate reconciler - cr = &webhook.CertReconciler{ - Clientset: cs, - } - if err := cr.SetupWithManager(handler, mgr, cs); err != nil { - return errors.Wrap(err, " unable to create webhook certificate controller") - } - - // Set up the webhooks - if err = (&coh.Coherence{}).SetupWebhookWithManager(mgr); err != nil { - return errors.Wrap(err, " unable to create webhook") - } - } else { - setupLog.Info("Operator is running with web-hooks disabled") - } - // Create the REST server restServer := rest.NewServer(cs.KubeClient) if err := restServer.SetupWithManager(mgr); err != nil { @@ -216,13 +196,6 @@ func execute() error { // +kubebuilder:scaffold:builder - go func() { - <-handler.Done() - if cr != nil { - cr.Cleanup() - } - }() - setupLog.Info("starting manager") if err := mgr.Start(handler); err != nil { setupLog.Error(err, "problem running manager") From 66fbc09184c40858ac2462e4004c7dbc15063db5 Mon Sep 17 00:00:00 2001 From: Jonathan Knight Date: Wed, 26 Feb 2025 18:03:21 +0300 Subject: [PATCH 2/4] Fixing RBAC --- api/v1/validate.go | 2 +- api/v1/zz_generated.deepcopy.go | 2 +- config/rbac/crd_install_role.yaml | 21 ++++++++++ config/rbac/crd_install_role_binding.yaml | 19 +++++++++ config/rbac/kustomization.yaml | 6 +++ config/rbac/leader_election_role.yaml | 2 +- config/rbac/leader_election_role_binding.yaml | 6 +-- config/rbac/node_viewer_role.yaml | 2 +- config/rbac/role.yaml | 13 ------- config/rbac/role_binding.yaml | 2 +- config/rbac/service_account.yaml | 3 +- controllers/coherence_controller.go | 2 +- controllers/coherencejob_controller.go | 2 +- controllers/common.go | 2 +- controllers/job/job_controller.go | 2 +- .../servicemonitor_controller.go | 2 +- go.mod | 4 +- .../coherence-operator/templates/rbac.yaml | 39 +++++++------------ helm-charts/coherence-operator/values.yaml | 2 +- pkg/operator/operator.go | 2 +- pkg/runner/cmd_operator.go | 2 +- test/e2e/helm/helm_test.go | 25 +----------- 22 files changed, 80 insertions(+), 82 deletions(-) create mode 100644 config/rbac/crd_install_role.yaml create mode 100644 config/rbac/crd_install_role_binding.yaml diff --git a/api/v1/validate.go b/api/v1/validate.go index fc62d1d98..c36079252 100644 --- a/api/v1/validate.go +++ b/api/v1/validate.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. * Licensed under the Universal Permissive License v 1.0 as shown at * http://oss.oracle.com/licenses/upl. */ diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 4d6cf33ac..11333bf02 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ //go:build !ignore_autogenerated /* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. * Licensed under the Universal Permissive License v 1.0 as shown at * http://oss.oracle.com/licenses/upl. */ diff --git a/config/rbac/crd_install_role.yaml b/config/rbac/crd_install_role.yaml new file mode 100644 index 000000000..91887c007 --- /dev/null +++ b/config/rbac/crd_install_role.yaml @@ -0,0 +1,21 @@ +# ------------------------------------------------------------- +# This is the Cluster Roles required by the Coherence Operator +# to self-manage its CRDs and Web-Hooks. +# ------------------------------------------------------------- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: crd-install-role + labels: + app.kubernetes.io/name: coherence-operator + app.kubernetes.io/managed-by: kustomize +rules: + - apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - create + - delete + - get + - update diff --git a/config/rbac/crd_install_role_binding.yaml b/config/rbac/crd_install_role_binding.yaml new file mode 100644 index 000000000..64d0d9f39 --- /dev/null +++ b/config/rbac/crd_install_role_binding.yaml @@ -0,0 +1,19 @@ +# -------------------------------------------------------------------- +# This is the Cluster Role binding required by the Coherence Operator +# to self-manage its CRDs and Web-Hooks. +# -------------------------------------------------------------------- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: crd-install-rolebinding + labels: + app.kubernetes.io/name: coherence-operator + app.kubernetes.io/managed-by: kustomize +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: crd-install-role +subjects: +- kind: ServiceAccount + name: service-account + namespace: default diff --git a/config/rbac/kustomization.yaml b/config/rbac/kustomization.yaml index 76efc18e4..629dde45c 100644 --- a/config/rbac/kustomization.yaml +++ b/config/rbac/kustomization.yaml @@ -11,6 +11,12 @@ resources: - node_viewer_role_binding.yaml - leader_election_role.yaml - leader_election_role_binding.yaml + # These roles allow the Operator to install its own CRD + # If these are removed the Operator must be run with the + # command line arguments to not install CRDs and the CRDs + # must already be manually installed + - crd_install_role.yaml + - crd_install_role_binding.yaml # The following RBAC configurations are used to protect # the metrics endpoint with authn/authz. These configurations # ensure that only authorized users and service accounts diff --git a/config/rbac/leader_election_role.yaml b/config/rbac/leader_election_role.yaml index daa22601a..2c9fcd943 100644 --- a/config/rbac/leader_election_role.yaml +++ b/config/rbac/leader_election_role.yaml @@ -2,10 +2,10 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: + name: leader-election-role labels: app.kubernetes.io/name: coherence-operator app.kubernetes.io/managed-by: kustomize - name: leader-election-role rules: - apiGroups: - "" diff --git a/config/rbac/leader_election_role_binding.yaml b/config/rbac/leader_election_role_binding.yaml index 99f0dffd5..9325347b9 100644 --- a/config/rbac/leader_election_role_binding.yaml +++ b/config/rbac/leader_election_role_binding.yaml @@ -1,15 +1,15 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: + name: leader-election-rolebinding labels: app.kubernetes.io/name: coherence-operator app.kubernetes.io/managed-by: kustomize - name: leader-election-rolebinding roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: leader-election-role subjects: - kind: ServiceAccount - name: controller-manager - namespace: system + name: service-account + namespace: default diff --git a/config/rbac/node_viewer_role.yaml b/config/rbac/node_viewer_role.yaml index aeb4be64a..0847dbba1 100644 --- a/config/rbac/node_viewer_role.yaml +++ b/config/rbac/node_viewer_role.yaml @@ -7,10 +7,10 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: + name: node-viewer-role labels: app.kubernetes.io/name: coherence-operator app.kubernetes.io/managed-by: kustomize - name: node-viewer-role rules: - apiGroups: - "" diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 94b042ff7..fd26d3f25 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -46,19 +46,6 @@ rules: - patch - update - watch -- apiGroups: - - cert-manager.io - resources: - - certificates - - issuers - verbs: - - create - - delete - - get - - list - - patch - - update - - watch - apiGroups: - coherence.oracle.com resources: diff --git a/config/rbac/role_binding.yaml b/config/rbac/role_binding.yaml index f0d203381..5d4e6386b 100644 --- a/config/rbac/role_binding.yaml +++ b/config/rbac/role_binding.yaml @@ -5,10 +5,10 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: + name: manager-rolebinding labels: app.kubernetes.io/name: coherence-operator app.kubernetes.io/managed-by: kustomize - name: manager-rolebinding roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole diff --git a/config/rbac/service_account.yaml b/config/rbac/service_account.yaml index 2d2af29c0..b70cfd404 100644 --- a/config/rbac/service_account.yaml +++ b/config/rbac/service_account.yaml @@ -4,8 +4,7 @@ apiVersion: v1 kind: ServiceAccount metadata: + name: service-account labels: app.kubernetes.io/name: coherence-operator app.kubernetes.io/managed-by: kustomize - name: controller-manager - namespace: system diff --git a/controllers/coherence_controller.go b/controllers/coherence_controller.go index f91adf27a..d1e6f393a 100644 --- a/controllers/coherence_controller.go +++ b/controllers/coherence_controller.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. * Licensed under the Universal Permissive License v 1.0 as shown at * http://oss.oracle.com/licenses/upl. */ diff --git a/controllers/coherencejob_controller.go b/controllers/coherencejob_controller.go index b67b0ce8b..b84f4d503 100644 --- a/controllers/coherencejob_controller.go +++ b/controllers/coherencejob_controller.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. * Licensed under the Universal Permissive License v 1.0 as shown at * http://oss.oracle.com/licenses/upl. */ diff --git a/controllers/common.go b/controllers/common.go index 888ebd825..7264634c7 100644 --- a/controllers/common.go +++ b/controllers/common.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. * Licensed under the Universal Permissive License v 1.0 as shown at * http://oss.oracle.com/licenses/upl. */ diff --git a/controllers/job/job_controller.go b/controllers/job/job_controller.go index 5c87fd362..69c50018c 100644 --- a/controllers/job/job_controller.go +++ b/controllers/job/job_controller.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. * Licensed under the Universal Permissive License v 1.0 as shown at * http://oss.oracle.com/licenses/upl. */ diff --git a/controllers/servicemonitor/servicemonitor_controller.go b/controllers/servicemonitor/servicemonitor_controller.go index 7f3d9b14d..c6a9d95b5 100644 --- a/controllers/servicemonitor/servicemonitor_controller.go +++ b/controllers/servicemonitor/servicemonitor_controller.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. * Licensed under the Universal Permissive License v 1.0 as shown at * http://oss.oracle.com/licenses/upl. */ diff --git a/go.mod b/go.mod index ccfa8b973..559ea60a2 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,6 @@ require ( github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 github.com/go-logr/logr v1.4.2 github.com/go-test/deep v1.1.1 - github.com/onsi/ginkgo/v2 v2.22.2 github.com/onsi/gomega v1.36.2 github.com/pkg/errors v0.9.1 github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.80.1 @@ -39,7 +38,6 @@ require ( github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect - github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.3 // indirect @@ -62,6 +60,7 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect + github.com/onsi/ginkgo/v2 v2.22.2 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/prometheus/client_golang v1.21.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect @@ -84,7 +83,6 @@ require ( golang.org/x/term v0.29.0 // indirect golang.org/x/text v0.22.0 // indirect golang.org/x/time v0.10.0 // indirect - golang.org/x/tools v0.30.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/protobuf v1.36.5 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect diff --git a/helm-charts/coherence-operator/templates/rbac.yaml b/helm-charts/coherence-operator/templates/rbac.yaml index e01086ec7..0ef330ef2 100644 --- a/helm-charts/coherence-operator/templates/rbac.yaml +++ b/helm-charts/coherence-operator/templates/rbac.yaml @@ -7,7 +7,7 @@ metadata: name: {{ default "coherence-operator" .Values.serviceAccountName }} namespace: {{ .Release.Namespace }} labels: - control-plane: coherence + app.kubernetes.io/name: coherence-operator {{- if (.Values.globalLabels) }} {{ toYaml .Values.globalLabels | indent 4 }} {{- end }} @@ -19,15 +19,15 @@ metadata: {{- if .Values.clusterRoles }} # ------------------------------------------------------------- # This is the Cluster Roles required by the Coherence Operator -# to self-manage its CRDs and Web-Hooks. +# to self-manage its CRDs. # ------------------------------------------------------------- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: coherence-operator-crd-webhook-install + name: coherence-operator-crd-install namespace: {{ .Release.Namespace }} labels: - control-plane: coherence + app.kubernetes.io/name: coherence-operator {{- if (.Values.globalLabels) }} {{ toYaml .Values.globalLabels | indent 4 }} {{- end }} @@ -45,29 +45,18 @@ rules: - delete - get - update - - apiGroups: - - admissionregistration.k8s.io - resources: - - mutatingwebhookconfigurations - - validatingwebhookconfigurations - verbs: - - create - - delete - - get - - update - - watch --- # -------------------------------------------------------------------- # This is the Cluster Role binding required by the Coherence Operator -# to self-manage its CRDs and Web-Hooks. +# to self-manage its CRDs. # -------------------------------------------------------------------- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: - name: coherence-operator-crd-webhook-install + name: coherence-operator-crd-install namespace: {{ .Release.Namespace }} labels: - control-plane: coherence + app.kubernetes.io/name: coherence-operator {{- if (.Values.globalLabels) }} {{ toYaml .Values.globalLabels | indent 4 }} {{- end }} @@ -78,7 +67,7 @@ metadata: roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole - name: coherence-operator-crd-webhook-install + name: coherence-operator-crd-install subjects: - kind: ServiceAccount name: {{ default "coherence-operator" .Values.serviceAccountName }} @@ -98,7 +87,7 @@ metadata: name: coherence-operator-node-viewer namespace: {{ .Release.Namespace }} labels: - control-plane: coherence + app.kubernetes.io/name: coherence-operator {{- if (.Values.globalLabels) }} {{ toYaml .Values.globalLabels | indent 4 }} {{- end }} @@ -126,7 +115,7 @@ metadata: name: coherence-operator-node-viewer namespace: {{ .Release.Namespace }} labels: - control-plane: coherence + app.kubernetes.io/name: coherence-operator {{- if (.Values.globalLabels) }} {{ toYaml .Values.globalLabels | indent 4 }} {{- end }} @@ -158,7 +147,7 @@ metadata: name: coherence-operator namespace: {{ .Release.Namespace }} labels: - control-plane: coherence + app.kubernetes.io/name: coherence-operator {{- if (.Values.globalLabels) }} {{ toYaml .Values.globalLabels | indent 4 }} {{- end }} @@ -253,7 +242,7 @@ metadata: name: coherence-operator namespace: {{ .Release.Namespace }} labels: - control-plane: coherence + app.kubernetes.io/name: coherence-operator {{- if (.Values.globalLabels) }} {{ toYaml .Values.globalLabels | indent 4 }} {{- end }} @@ -283,7 +272,7 @@ kind: Role metadata: name: leader-election-role labels: - control-plane: coherence + app.kubernetes.io/name: coherence-operator {{- if (.Values.globalLabels) }} {{ toYaml .Values.globalLabels | indent 4 }} {{- end }} @@ -333,7 +322,7 @@ kind: RoleBinding metadata: name: leader-election-rolebinding labels: - control-plane: coherence + app.kubernetes.io/name: coherence-operator {{- if (.Values.globalLabels) }} {{ toYaml .Values.globalLabels | indent 4 }} {{- end }} diff --git a/helm-charts/coherence-operator/values.yaml b/helm-charts/coherence-operator/values.yaml index f5b11f476..259f64244 100644 --- a/helm-charts/coherence-operator/values.yaml +++ b/helm-charts/coherence-operator/values.yaml @@ -15,7 +15,7 @@ defaultCoherenceImage: name: "${COHERENCE_IMAGE_NAME}" tag: "${COHERENCE_IMAGE_TAG}" -# watchNamespaces is the comma delimited list of namespaces that the operator should +# watchNamespaces is the comma-delimited list of namespaces that the operator should # manage Coherence resources in. The default is to manage all namespaces. watchNamespaces: "" diff --git a/pkg/operator/operator.go b/pkg/operator/operator.go index 31f39473c..3c91a060d 100644 --- a/pkg/operator/operator.go +++ b/pkg/operator/operator.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. * Licensed under the Universal Permissive License v 1.0 as shown at * http://oss.oracle.com/licenses/upl. */ diff --git a/pkg/runner/cmd_operator.go b/pkg/runner/cmd_operator.go index 00a2e9db1..e99d6c523 100644 --- a/pkg/runner/cmd_operator.go +++ b/pkg/runner/cmd_operator.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. * Licensed under the Universal Permissive License v 1.0 as shown at * http://oss.oracle.com/licenses/upl. */ diff --git a/test/e2e/helm/helm_test.go b/test/e2e/helm/helm_test.go index dd5248494..70be70fa5 100644 --- a/test/e2e/helm/helm_test.go +++ b/test/e2e/helm/helm_test.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. * Licensed under the Universal Permissive License v 1.0 as shown at * http://oss.oracle.com/licenses/upl. */ @@ -922,6 +922,7 @@ func AssertHelmInstallWithSubTest(t *testing.T, id string, cmd *exec.Cmd, g *Gom } func AssertHelmInstallWithStatefulSetSubTest(t *testing.T, id string, cmd *exec.Cmd, g *GomegaWithT, test StatefulSetSubTest) { + var err error ns := helper.GetTestNamespace() t.Cleanup(func() { @@ -931,9 +932,6 @@ func AssertHelmInstallWithStatefulSetSubTest(t *testing.T, id string, cmd *exec. Cleanup(ns, "operator") }) - t.Logf("Asserting Helm install. Removing Webhooks") - err := RemoveWebHook() - g.Expect(err).NotTo(HaveOccurred()) t.Logf("Asserting Helm install. Removing RBAC") err = RemoveRBAC() g.Expect(err).NotTo(HaveOccurred()) @@ -986,25 +984,6 @@ func Cleanup(namespace, name string) { _ = helper.WaitForDeleteOfPodsWithSelector(testContext, namespace, "control-plane=coherence", helper.RetryInterval, helper.Timeout) } -// Remove the web-hooks that the Operator install creates to -// ensure that nothing is left from a previous test. -func RemoveWebHook() error { - // DefaultValidatingWebhookName - client := testContext.KubeClient.AdmissionregistrationV1() - - err := client.MutatingWebhookConfigurations().Delete(goctx.TODO(), operator.DefaultMutatingWebhookName, metav1.DeleteOptions{}) - if err != nil && !errors.IsNotFound(err) { - return err - } - - err = client.ValidatingWebhookConfigurations().Delete(goctx.TODO(), operator.DefaultValidatingWebhookName, metav1.DeleteOptions{}) - if err != nil && !errors.IsNotFound(err) { - return err - } - - return nil -} - // Remove the CRDs that the Operator install creates. func RemoveCRDs() error { cohCrd := crdv1.CustomResourceDefinition{} From c4b8bbeebb6e902929c22726e4fd8167b2f95994 Mon Sep 17 00:00:00 2001 From: Jonathan Knight Date: Wed, 26 Feb 2025 20:11:57 +0300 Subject: [PATCH 3/4] Fix kustomize replacements --- config/default/kustomization.yaml | 13 +++++++++++++ config/manager/manager.yaml | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml index 9f8450407..bcefaf947 100644 --- a/config/default/kustomization.yaml +++ b/config/default/kustomization.yaml @@ -20,3 +20,16 @@ resources: # Only CR(s) which requires webhooks and are applied on namespaces labeled with 'webhooks: enabled' will # be able to communicate with the Webhook Server. #- ../network-policy + +# Patch the SERVICE_NAME env var in the Operator Deployment +# with the name of the Operator REST service. +replacements: + - source: + kind: Service + name: rest + targets: + - select: + kind: Deployment + name: controller-manager + fieldPaths: + - spec.template.spec.containers.0.env.[name=SERVICE_NAME].value diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index f68f572fd..eb5e41f2d 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -69,7 +69,7 @@ spec: fieldRef: fieldPath: metadata.name - name: SERVICE_NAME - value: $(REST_SERVICE_NAME) + value: REST_SERVICE_NAME readinessProbe: httpGet: port: health From e52647a117e8b7d62d27d91424517f9f45ca37ff Mon Sep 17 00:00:00 2001 From: Jonathan Knight Date: Thu, 27 Feb 2025 10:32:59 +0300 Subject: [PATCH 4/4] Fix web hook patching --- Makefile | 2 +- config/manager/hooks.yaml | 32 +++++++++++++ config/manager/kustomization.yaml | 3 +- config/overlays/restricted/kustomization.yaml | 2 + .../overlays/restricted/mutating-webhook.yaml | 10 ++++ .../overlays/restricted/node-viewer-role.yaml | 4 ++ .../restricted/node_viewer_role_binding.yaml | 4 ++ .../restricted/single-namespace-patch.yaml | 4 ++ .../restricted/validating-webhook.yaml | 10 ++++ .../coherence-operator/templates/hooks.yaml | 48 +++++++++++++++++++ helm-charts/coherence-operator/values.yaml | 8 ++++ 11 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 config/manager/hooks.yaml create mode 100644 config/overlays/restricted/mutating-webhook.yaml create mode 100644 config/overlays/restricted/validating-webhook.yaml create mode 100644 helm-charts/coherence-operator/templates/hooks.yaml diff --git a/Makefile b/Makefile index a6b2af3d6..14f301a0e 100644 --- a/Makefile +++ b/Makefile @@ -1733,7 +1733,7 @@ $(BUILD_MANIFESTS_PKG): $(TOOLS_BIN)/kustomize $(TOOLS_BIN)/yq $(MANIFEST_FILES) $(SED) -e 's/name: coherence-operator-env-vars-.*/name: coherence-operator-env-vars/g' $(BUILD_OUTPUT)/coherence-operator.yaml $(KUSTOMIZE) build $(BUILD_DEPLOY)/overlays/restricted >> $(BUILD_OUTPUT)/coherence-operator-restricted.yaml $(SED) -e 's/name: coherence-operator-env-vars-.*/name: coherence-operator-env-vars/g' $(BUILD_OUTPUT)//coherence-operator-restricted.yaml - $(SED) -e 's/ClusterRole/Role/g' $(BUILD_OUTPUT)//coherence-operator-restricted.yaml + $(SED) -e 's/ClusterRole/Role/g' $(BUILD_OUTPUT)/coherence-operator-restricted.yaml cd $(BUILD_MANIFESTS)/crd && $(TOOLS_BIN)/yq --no-doc -s '.metadata.name + ".yaml"' temp.yaml rm $(BUILD_MANIFESTS)/crd/temp.yaml mv $(BUILD_MANIFESTS)/crd/coherence.coherence.oracle.com.yaml $(BUILD_MANIFESTS)/crd/coherence.oracle.com_coherence.yaml diff --git a/config/manager/hooks.yaml b/config/manager/hooks.yaml new file mode 100644 index 000000000..2e9e6313b --- /dev/null +++ b/config/manager/hooks.yaml @@ -0,0 +1,32 @@ +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: validating-webhook-configuration +webhooks: + - name: coherence.oracle.com + admissionReviewVersions: + - v1 + rules: [] + sideEffects: None + clientConfig: + service: + name: webhook + namespace: default +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: mutating-webhook-configuration +webhooks: + - name: coherence.oracle.com + admissionReviewVersions: + - v1 + rules: [] + sideEffects: None + clientConfig: + service: + name: webhook + namespace: default + + diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 7539525f4..19aa35605 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -2,8 +2,9 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- manager.yaml +- hooks.yaml - service.yaml +- manager.yaml images: - name: controller diff --git a/config/overlays/restricted/kustomization.yaml b/config/overlays/restricted/kustomization.yaml index 10acc1d27..accc064cd 100644 --- a/config/overlays/restricted/kustomization.yaml +++ b/config/overlays/restricted/kustomization.yaml @@ -9,5 +9,7 @@ patches: target: kind: Deployment name: controller-manager + - path: validating-webhook.yaml + - path: mutating-webhook.yaml - path: node-viewer-role.yaml - path: node_viewer_role_binding.yaml diff --git a/config/overlays/restricted/mutating-webhook.yaml b/config/overlays/restricted/mutating-webhook.yaml new file mode 100644 index 000000000..ae299e650 --- /dev/null +++ b/config/overlays/restricted/mutating-webhook.yaml @@ -0,0 +1,10 @@ +# +# This patch will remove the mutating webhook +# +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: mutating-webhook-configuration +$patch: delete + diff --git a/config/overlays/restricted/node-viewer-role.yaml b/config/overlays/restricted/node-viewer-role.yaml index 4dfb96767..9e842be43 100644 --- a/config/overlays/restricted/node-viewer-role.yaml +++ b/config/overlays/restricted/node-viewer-role.yaml @@ -1,3 +1,7 @@ +# +# This patch will remove the ClusterRole to allow the +# Operator to read Node labels. +# apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: diff --git a/config/overlays/restricted/node_viewer_role_binding.yaml b/config/overlays/restricted/node_viewer_role_binding.yaml index 9e035b494..8807fb0b2 100644 --- a/config/overlays/restricted/node_viewer_role_binding.yaml +++ b/config/overlays/restricted/node_viewer_role_binding.yaml @@ -1,3 +1,7 @@ +# +# This patch will remove the ClusterRoleBinding to allow the +# Operator to read Node labels. +# apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: diff --git a/config/overlays/restricted/single-namespace-patch.yaml b/config/overlays/restricted/single-namespace-patch.yaml index 6b5d7a6c6..769181b27 100644 --- a/config/overlays/restricted/single-namespace-patch.yaml +++ b/config/overlays/restricted/single-namespace-patch.yaml @@ -1,3 +1,7 @@ +# +# This patch will configure the Operator to only manage the +# single namespace that it is installed into. +# - op: add path: /spec/template/spec/containers/0/env/- value: diff --git a/config/overlays/restricted/validating-webhook.yaml b/config/overlays/restricted/validating-webhook.yaml new file mode 100644 index 000000000..d4273c7c2 --- /dev/null +++ b/config/overlays/restricted/validating-webhook.yaml @@ -0,0 +1,10 @@ +# +# This patch will remove the validating webhook +# +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: validating-webhook-configuration +$patch: delete + diff --git a/helm-charts/coherence-operator/templates/hooks.yaml b/helm-charts/coherence-operator/templates/hooks.yaml new file mode 100644 index 000000000..490933544 --- /dev/null +++ b/helm-charts/coherence-operator/templates/hooks.yaml @@ -0,0 +1,48 @@ +# ------------------------------------------------------------- +# This file is used to overwrite any previous web-hooks that +# a previous Coherence Operator install may have created. +# The current operator version does not use web-hooks so any +# existing web-hooks will need to be deleted. As Helm does +# not really do deletes as part of an install instead we create +# the web-hooks with what is effectively a no-op configuration. +# The web-hooks configurations will never actually match any +# resources so the API server will never attempt to call the +# now non-existent server. +# +# If it is known that the old Operator web-hooks configurations +# do not exist, then the chart can be installed with the +# webhooks value in the values.yaml file set to false. +# +# ------------------------------------------------------------- +{{- if (.Values.webhooks) }} +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: validating-webhook-configuration +webhooks: + - name: coherence.oracle.com + admissionReviewVersions: + - v1 + rules: [] + sideEffects: None + clientConfig: + service: + name: webhook + namespace: default +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: mutating-webhook-configuration +webhooks: + - name: coherence.oracle.com + admissionReviewVersions: + - v1 + rules: [] + sideEffects: None + clientConfig: + service: + name: webhook + namespace: default +{{- end }} diff --git a/helm-charts/coherence-operator/values.yaml b/helm-charts/coherence-operator/values.yaml index 259f64244..3f1396441 100644 --- a/helm-charts/coherence-operator/values.yaml +++ b/helm-charts/coherence-operator/values.yaml @@ -194,6 +194,14 @@ clusterRoles: true # The default is true. nodeRoles: false +# The Coherence Operator no longer uses a web-hook. +# If a previous instance f the Operator was installed that included web-hook these will need to be disabled. +# otherwise it will not be possible to create or update Coherence resources. +# When this flag has a value of true (which is the default) web-hook configurations will be created that will +# be no-op configurations and will override any previous web-hooks that the old Operator created. +# If the previous Operator install did not use web-hooks this value can be set to false +webhooks: true + # If set to false, the Operator will not support the CoherenceJob resource type. # The CoherenceJob CRD will not be installed by the Operator and the Operator will # not listen for any CoherenceJob resource events.