diff --git a/Makefile b/Makefile index 1d3a8dfb..f1e863ff 100644 --- a/Makefile +++ b/Makefile @@ -50,7 +50,17 @@ $(KUBEBUILDER_ASSETS): ### Targets .PHONY: unit-tests -unit-tests: install-tools $(KUBEBUILDER_ASSETS) generate fmt vet vuln manifests ## Run unit tests +unit-tests::install-tools ## Run unit tests +unit-test::$(KUBEBUILDER_ASSETS) +unit-test::generate +unit-test::fmt +unit-test::vet +unit-test::vuln +unit-test::manifests +unit-test::just-unit-tests + +.PHONY: just-unit-tests +just-unit-tests: ginkgo -r --randomize-all api/ internal/ rabbitmqclient/ .PHONY: integration-tests @@ -63,7 +73,7 @@ just-integration-tests: $(KUBEBUILDER_ASSETS) vet local-tests: unit-tests integration-tests ## Run all local tests (unit & integration) system-tests: ## run end-to-end tests against Kubernetes cluster defined in ~/.kube/config. Expects cluster operator and messaging topology operator to be installed in the cluster - NAMESPACE="rabbitmq-system" ginkgo --randomize-all -r system_tests/ + NAMESPACE="rabbitmq-system" ginkgo --randomize-all -r $(GINKGO_EXTRA) system_tests/ # Build manager binary manager: generate fmt vet vuln @@ -207,7 +217,7 @@ generate-manifests: # Cert Manager # ################ -CERT_MANAGER_VERSION ?= v1.7.0 +CERT_MANAGER_VERSION ?= v1.12.3 CERT_MANAGER_MANIFEST ?= https://github.com/jetstack/cert-manager/releases/download/$(CERT_MANAGER_VERSION)/cert-manager.yaml CMCTL = $(LOCAL_BIN)/cmctl diff --git a/api/v1alpha1/superstream_types.go b/api/v1alpha1/superstream_types.go index 93ecb477..91234633 100644 --- a/api/v1alpha1/superstream_types.go +++ b/api/v1alpha1/superstream_types.go @@ -70,10 +70,10 @@ type SuperStreamList struct { Items []SuperStream `json:"items"` } -func (q *SuperStream) GroupResource() schema.GroupResource { +func (s *SuperStream) GroupResource() schema.GroupResource { return schema.GroupResource{ - Group: q.GroupVersionKind().Group, - Resource: q.GroupVersionKind().Kind, + Group: s.GroupVersionKind().Group, + Resource: s.GroupVersionKind().Kind, } } diff --git a/api/v1alpha1/superstream_webhook.go b/api/v1alpha1/superstream_webhook.go index 91f73449..e0d8b82c 100644 --- a/api/v1alpha1/superstream_webhook.go +++ b/api/v1alpha1/superstream_webhook.go @@ -10,6 +10,7 @@ This product may include a number of subcomponents with separate copyright notic package v1alpha1 import ( + "context" "fmt" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -27,43 +28,52 @@ func (s *SuperStream) SetupWebhookWithManager(mgr ctrl.Manager) error { // +kubebuilder:webhook:verbs=create;update,path=/validate-rabbitmq-com-v1alpha1-superstream,mutating=false,failurePolicy=fail,groups=rabbitmq.com,resources=superstreams,versions=v1alpha1,name=vsuperstream.kb.io,sideEffects=none,admissionReviewVersions=v1 -var _ webhook.Validator = &SuperStream{} +var _ webhook.CustomValidator = &SuperStream{} -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type -// either rabbitmqClusterReference.name or rabbitmqClusterReference.connectionSecret must be provided but not both -func (s *SuperStream) ValidateCreate() (admission.Warnings, error) { - return s.Spec.RabbitmqClusterReference.ValidateOnCreate(s.GroupResource(), s.Name) +// ValidateCreate - either rabbitmqClusterReference.name or +// rabbitmqClusterReference.connectionSecret must be provided but not both +func (s *SuperStream) ValidateCreate(_ context.Context, obj runtime.Object) (warnings admission.Warnings, err error) { + ss, ok := obj.(*SuperStream) + if !ok { + return nil, fmt.Errorf("expected a RabbitMQ super stream but got a %T", obj) + } + return ss.Spec.RabbitmqClusterReference.ValidateOnCreate(ss.GroupResource(), ss.Name) } // ValidateUpdate returns error type 'forbidden' for updates on superstream name, vhost and rabbitmqClusterReference -func (s *SuperStream) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - oldSuperStream, ok := old.(*SuperStream) +func (s *SuperStream) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (warnings admission.Warnings, err error) { + oldSuperStream, ok := oldObj.(*SuperStream) + if !ok { + return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a superstream but got a %T", oldObj)) + } + + newSuperStream, ok := newObj.(*SuperStream) if !ok { - return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a superstream but got a %T", old)) + return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a superstream but got a %T", newObj)) } - detailMsg := "updates on name, vhost and rabbitmqClusterReference are all forbidden" - if s.Spec.Name != oldSuperStream.Spec.Name { - return nil, apierrors.NewForbidden(s.GroupResource(), s.Name, + const detailMsg = "updates on name, vhost and rabbitmqClusterReference are all forbidden" + if newSuperStream.Spec.Name != oldSuperStream.Spec.Name { + return nil, apierrors.NewForbidden(newSuperStream.GroupResource(), newSuperStream.Name, field.Forbidden(field.NewPath("spec", "name"), detailMsg)) } - if s.Spec.Vhost != oldSuperStream.Spec.Vhost { - return nil, apierrors.NewForbidden(s.GroupResource(), s.Name, + if newSuperStream.Spec.Vhost != oldSuperStream.Spec.Vhost { + return nil, apierrors.NewForbidden(newSuperStream.GroupResource(), newSuperStream.Name, field.Forbidden(field.NewPath("spec", "vhost"), detailMsg)) } - if !oldSuperStream.Spec.RabbitmqClusterReference.Matches(&s.Spec.RabbitmqClusterReference) { - return nil, apierrors.NewForbidden(s.GroupResource(), s.Name, + if !oldSuperStream.Spec.RabbitmqClusterReference.Matches(&newSuperStream.Spec.RabbitmqClusterReference) { + return nil, apierrors.NewForbidden(newSuperStream.GroupResource(), newSuperStream.Name, field.Forbidden(field.NewPath("spec", "rabbitmqClusterReference"), detailMsg)) } - if !routingKeyUpdatePermitted(oldSuperStream.Spec.RoutingKeys, s.Spec.RoutingKeys) { - return nil, apierrors.NewForbidden(s.GroupResource(), s.Name, + if !routingKeyUpdatePermitted(oldSuperStream.Spec.RoutingKeys, newSuperStream.Spec.RoutingKeys) { + return nil, apierrors.NewForbidden(newSuperStream.GroupResource(), newSuperStream.Name, field.Forbidden(field.NewPath("spec", "routingKeys"), "updates may only add to the existing list of routing keys")) } - if s.Spec.Partitions < oldSuperStream.Spec.Partitions { - return nil, apierrors.NewForbidden(s.GroupResource(), s.Name, + if newSuperStream.Spec.Partitions < oldSuperStream.Spec.Partitions { + return nil, apierrors.NewForbidden(newSuperStream.GroupResource(), newSuperStream.Name, field.Forbidden(field.NewPath("spec", "partitions"), "updates may only increase the partition count, and may not decrease it")) } @@ -71,7 +81,7 @@ func (s *SuperStream) ValidateUpdate(old runtime.Object) (admission.Warnings, er } // ValidateDelete no validation on delete -func (s *SuperStream) ValidateDelete() (admission.Warnings, error) { +func (s *SuperStream) ValidateDelete(_ context.Context, _ runtime.Object) (warnings admission.Warnings, err error) { return nil, nil } diff --git a/api/v1alpha1/superstream_webhook_test.go b/api/v1alpha1/superstream_webhook_test.go index 7e063225..c5bf8889 100644 --- a/api/v1alpha1/superstream_webhook_test.go +++ b/api/v1alpha1/superstream_webhook_test.go @@ -1,6 +1,7 @@ package v1alpha1 import ( + "context" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" topologyv1beta1 "github.com/rabbitmq/messaging-topology-operator/api/v1beta1" @@ -10,7 +11,11 @@ import ( ) var _ = Describe("superstream webhook", func() { - var superstream = SuperStream{} + var ( + superstream = SuperStream{} + rootCtx = context.Background() + ) + BeforeEach(func() { superstream = SuperStream{ ObjectMeta: metav1.ObjectMeta{ @@ -31,16 +36,16 @@ var _ = Describe("superstream webhook", func() { It("does not allow both spec.rabbitmqClusterReference.name and spec.rabbitmqClusterReference.connectionSecret be configured", func() { notAllowed := superstream.DeepCopy() notAllowed.Spec.RabbitmqClusterReference.ConnectionSecret = &corev1.LocalObjectReference{Name: "some-secret"} - _, err := notAllowed.ValidateCreate() - Expect(apierrors.IsForbidden(err)).To(BeTrue()) + _, err := notAllowed.ValidateCreate(rootCtx, notAllowed) + Expect(err).To(MatchError(ContainSubstring("invalid RabbitmqClusterReference: do not provide both name and connectionSecret"))) }) It("spec.rabbitmqClusterReference.name and spec.rabbitmqClusterReference.connectionSecret cannot both be empty", func() { notAllowed := superstream.DeepCopy() notAllowed.Spec.RabbitmqClusterReference.Name = "" notAllowed.Spec.RabbitmqClusterReference.ConnectionSecret = nil - _, err := notAllowed.ValidateCreate() - Expect(apierrors.IsForbidden(err)).To(BeTrue()) + _, err := notAllowed.ValidateCreate(rootCtx, notAllowed) + Expect(err).To(MatchError(ContainSubstring("invalid RabbitmqClusterReference: must provide either name or connectionSecret"))) }) }) @@ -48,15 +53,17 @@ var _ = Describe("superstream webhook", func() { It("does not allow updates on superstream name", func() { newSuperStream := superstream.DeepCopy() newSuperStream.Spec.Name = "new-name" - _, err := newSuperStream.ValidateUpdate(&superstream) - Expect(apierrors.IsForbidden(err)).To(BeTrue()) + _, err := newSuperStream.ValidateUpdate(rootCtx, &superstream, newSuperStream) + Expect(apierrors.IsForbidden(err)).To(BeTrue(), "expected error type to be 'forbidden'") + Expect(err).To(MatchError(ContainSubstring("updates on name, vhost and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on superstream vhost", func() { newSuperStream := superstream.DeepCopy() newSuperStream.Spec.Vhost = "new-vhost" - _, err := newSuperStream.ValidateUpdate(&superstream) - Expect(apierrors.IsForbidden(err)).To(BeTrue()) + _, err := newSuperStream.ValidateUpdate(rootCtx, &superstream, newSuperStream) + Expect(apierrors.IsForbidden(err)).To(BeTrue(), "expected error type to be 'forbidden'") + Expect(err).To(MatchError(ContainSubstring("updates on name, vhost and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on RabbitmqClusterReference", func() { @@ -64,51 +71,56 @@ var _ = Describe("superstream webhook", func() { newSuperStream.Spec.RabbitmqClusterReference = topologyv1beta1.RabbitmqClusterReference{ Name: "new-cluster", } - _, err := newSuperStream.ValidateUpdate(&superstream) - Expect(apierrors.IsForbidden(err)).To(BeTrue()) + _, err := newSuperStream.ValidateUpdate(rootCtx, &superstream, newSuperStream) + Expect(apierrors.IsForbidden(err)).To(BeTrue(), "expected error type to be 'forbidden'") + Expect(err).To(MatchError(ContainSubstring("updates on name, vhost and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on rabbitmqClusterReference.connectionSecret", func() { newSuperStream := superstream.DeepCopy() newSuperStream.Spec.RabbitmqClusterReference = topologyv1beta1.RabbitmqClusterReference{ConnectionSecret: &corev1.LocalObjectReference{Name: "a-secret"}} - _, err := newSuperStream.ValidateUpdate(&superstream) - Expect(apierrors.IsForbidden(err)).To(BeTrue()) + _, err := newSuperStream.ValidateUpdate(rootCtx, &superstream, newSuperStream) + Expect(apierrors.IsForbidden(err)).To(BeTrue(), "expected error type to be 'forbidden'") + Expect(err).To(MatchError(ContainSubstring("updates on name, vhost and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on superstream.spec.routingKeys", func() { newSuperStream := superstream.DeepCopy() newSuperStream.Spec.RoutingKeys = []string{"a1", "d6"} - _, err := newSuperStream.ValidateUpdate(&superstream) - Expect(apierrors.IsForbidden(err)).To(BeTrue()) + _, err := newSuperStream.ValidateUpdate(rootCtx, &superstream, newSuperStream) + Expect(apierrors.IsForbidden(err)).To(BeTrue(), "expected error type to be 'forbidden'") + Expect(err).To(MatchError(ContainSubstring("updates may only add to the existing list of routing keys"))) }) - It("if the superstream previously had routing keys and the update only appends, the update succeeds", func() { + Specify("if the superstream previously had routing keys and the update only appends, the update succeeds", func() { newSuperStream := superstream.DeepCopy() newSuperStream.Spec.RoutingKeys = []string{"a1", "b2", "f17", "z66"} - _, err := newSuperStream.ValidateUpdate(&superstream) + _, err := newSuperStream.ValidateUpdate(rootCtx, &superstream, newSuperStream) Expect(err).NotTo(HaveOccurred()) }) - It("if the superstream previously had no routing keys but now does, the update fails", func() { + Specify("if the superstream previously had no routing keys but now does, the update fails", func() { superstream.Spec.RoutingKeys = nil newSuperStream := superstream.DeepCopy() newSuperStream.Spec.RoutingKeys = []string{"a1", "b2", "f17"} - _, err := newSuperStream.ValidateUpdate(&superstream) - Expect(apierrors.IsForbidden(err)).To(BeTrue()) + _, err := newSuperStream.ValidateUpdate(rootCtx, &superstream, newSuperStream) + Expect(apierrors.IsForbidden(err)).To(BeTrue(), "expected error type to be 'forbidden'") + Expect(err).To(MatchError(ContainSubstring("updates may only add to the existing list of routing keys"))) }) It("allows superstream.spec.partitions to be increased", func() { newSuperStream := superstream.DeepCopy() newSuperStream.Spec.Partitions = 1000 - _, err := newSuperStream.ValidateUpdate(&superstream) + _, err := newSuperStream.ValidateUpdate(rootCtx, &superstream, newSuperStream) Expect(err).NotTo(HaveOccurred()) }) It("does not allow superstream.spec.partitions to be decreased", func() { newSuperStream := superstream.DeepCopy() newSuperStream.Spec.Partitions = 1 - _, err := newSuperStream.ValidateUpdate(&superstream) - Expect(apierrors.IsForbidden(err)).To(BeTrue()) + _, err := newSuperStream.ValidateUpdate(rootCtx, &superstream, newSuperStream) + Expect(apierrors.IsForbidden(err)).To(BeTrue(), "expected error type to be 'forbidden'") + Expect(err).To(MatchError(ContainSubstring("updates may only increase the partition count, and may not decrease it"))) }) }) }) diff --git a/api/v1beta1/binding_webhook.go b/api/v1beta1/binding_webhook.go index f6a2de2c..09f13547 100644 --- a/api/v1beta1/binding_webhook.go +++ b/api/v1beta1/binding_webhook.go @@ -1,6 +1,7 @@ package v1beta1 import ( + "context" "fmt" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -13,76 +14,85 @@ import ( func (b *Binding) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). + WithValidator(b). For(b). Complete() } // +kubebuilder:webhook:verbs=create;update,path=/validate-rabbitmq-com-v1beta1-binding,mutating=false,failurePolicy=fail,groups=rabbitmq.com,resources=bindings,versions=v1beta1,name=vbinding.kb.io,sideEffects=none,admissionReviewVersions=v1 -var _ webhook.Validator = &Binding{} +var _ webhook.CustomValidator = &Binding{} // ValidateCreate implements webhook.Validator so a webhook will be registered for the type // either rabbitmqClusterReference.name or rabbitmqClusterReference.connectionSecret must be provided but not both -func (b *Binding) ValidateCreate() (admission.Warnings, error) { - return b.Spec.RabbitmqClusterReference.ValidateOnCreate(b.GroupResource(), b.Name) +func (b *Binding) ValidateCreate(_ context.Context, obj runtime.Object) (warnings admission.Warnings, err error) { + bi, ok := obj.(*Binding) + if !ok { + return nil, fmt.Errorf("expected a RabbitMQ Binding, but got %T", obj) + } + return nil, b.Spec.RabbitmqClusterReference.validate(bi.RabbitReference()) } -// ValidateUpdate updates on vhost and rabbitmqClusterReference are forbidden -func (b *Binding) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - oldBinding, ok := old.(*Binding) +func (b *Binding) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (warnings admission.Warnings, err error) { + oldBinding, ok := oldObj.(*Binding) + if !ok { + return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a binding but got a %T", oldObj)) + } + + newBinding, ok := newObj.(*Binding) if !ok { - return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a binding but got a %T", old)) + return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a binding but got a %T", oldObj)) } var allErrs field.ErrorList - detailMsg := "updates on vhost and rabbitmqClusterReference are all forbidden" + const detailMsg = "updates on vhost and rabbitmqClusterReference are all forbidden" - if b.Spec.Vhost != oldBinding.Spec.Vhost { - return nil, apierrors.NewForbidden(b.GroupResource(), b.Name, + if newBinding.Spec.Vhost != oldBinding.Spec.Vhost { + return nil, apierrors.NewForbidden(newBinding.GroupResource(), newBinding.Name, field.Forbidden(field.NewPath("spec", "vhost"), detailMsg)) } - if !oldBinding.Spec.RabbitmqClusterReference.Matches(&b.Spec.RabbitmqClusterReference) { - return nil, apierrors.NewForbidden(b.GroupResource(), b.Name, + if !oldBinding.Spec.RabbitmqClusterReference.Matches(&newBinding.Spec.RabbitmqClusterReference) { + return nil, apierrors.NewForbidden(newBinding.GroupResource(), newBinding.Name, field.Forbidden(field.NewPath("spec", "rabbitmqClusterReference"), detailMsg)) } - if b.Spec.Source != oldBinding.Spec.Source { + if newBinding.Spec.Source != oldBinding.Spec.Source { allErrs = append(allErrs, field.Invalid( field.NewPath("spec", "source"), - b.Spec.Source, + newBinding.Spec.Source, "source cannot be updated", )) } - if b.Spec.Destination != oldBinding.Spec.Destination { + if newBinding.Spec.Destination != oldBinding.Spec.Destination { allErrs = append(allErrs, field.Invalid( field.NewPath("spec", "destination"), - b.Spec.Destination, + newBinding.Spec.Destination, "destination cannot be updated", )) } - if b.Spec.DestinationType != oldBinding.Spec.DestinationType { + if newBinding.Spec.DestinationType != oldBinding.Spec.DestinationType { allErrs = append(allErrs, field.Invalid( field.NewPath("spec", "destinationType"), - b.Spec.DestinationType, + newBinding.Spec.DestinationType, "destinationType cannot be updated", )) } - if b.Spec.RoutingKey != oldBinding.Spec.RoutingKey { + if newBinding.Spec.RoutingKey != oldBinding.Spec.RoutingKey { allErrs = append(allErrs, field.Invalid( field.NewPath("spec", "routingKey"), - b.Spec.RoutingKey, + newBinding.Spec.RoutingKey, "routingKey cannot be updated", )) } - if !reflect.DeepEqual(b.Spec.Arguments, oldBinding.Spec.Arguments) { + if !reflect.DeepEqual(newBinding.Spec.Arguments, oldBinding.Spec.Arguments) { allErrs = append(allErrs, field.Invalid( field.NewPath("spec", "arguments"), - b.Spec.Arguments, + newBinding.Spec.Arguments, "arguments cannot be updated", )) } @@ -91,9 +101,9 @@ func (b *Binding) ValidateUpdate(old runtime.Object) (admission.Warnings, error) return nil, nil } - return nil, apierrors.NewInvalid(GroupVersion.WithKind("Binding").GroupKind(), b.Name, allErrs) + return nil, allErrs.ToAggregate() } -func (b *Binding) ValidateDelete() (admission.Warnings, error) { +func (b *Binding) ValidateDelete(_ context.Context, _ runtime.Object) (warnings admission.Warnings, err error) { return nil, nil } diff --git a/api/v1beta1/binding_webhook_test.go b/api/v1beta1/binding_webhook_test.go index fcf7bc4e..e5f3e2c9 100644 --- a/api/v1beta1/binding_webhook_test.go +++ b/api/v1beta1/binding_webhook_test.go @@ -1,43 +1,51 @@ package v1beta1 import ( + "context" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) var _ = Describe("Binding webhook", func() { - var oldBinding = Binding{ - ObjectMeta: metav1.ObjectMeta{ - Name: "update-binding", - }, - Spec: BindingSpec{ - Vhost: "/test", - Source: "test", - Destination: "test", - DestinationType: "queue", - RabbitmqClusterReference: RabbitmqClusterReference{ - Name: "some-cluster", + var ( + oldBinding = Binding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "update-binding", }, - }, - } + Spec: BindingSpec{ + Vhost: "/test", + Source: "test", + Destination: "test", + DestinationType: "queue", + RabbitmqClusterReference: RabbitmqClusterReference{ + Name: "some-cluster", + }, + }, + } + rootCtx = context.Background() + ) Context("ValidateCreate", func() { It("does not allow both spec.rabbitmqClusterReference.name and spec.rabbitmqClusterReference.connectionSecret be configured", func() { notAllowed := oldBinding.DeepCopy() notAllowed.Spec.RabbitmqClusterReference.ConnectionSecret = &corev1.LocalObjectReference{Name: "some-secret"} - Expect(apierrors.IsForbidden(ignoreNilWarning(notAllowed.ValidateCreate()))).To(BeTrue()) + + _, err := notAllowed.ValidateCreate(rootCtx, notAllowed) + Expect(err). + To(MatchError(ContainSubstring("invalid RabbitmqClusterReference: do not provide both name and connectionSecret"))) }) It("spec.rabbitmqClusterReference.name and spec.rabbitmqClusterReference.connectionSecret cannot both be empty", func() { notAllowed := oldBinding.DeepCopy() notAllowed.Spec.RabbitmqClusterReference.Name = "" notAllowed.Spec.RabbitmqClusterReference.ConnectionSecret = nil - Expect(apierrors.IsForbidden(ignoreNilWarning(notAllowed.ValidateCreate()))).To(BeTrue()) + + _, err := notAllowed.ValidateCreate(rootCtx, notAllowed) + Expect(err).To(MatchError(ContainSubstring("invalid RabbitmqClusterReference: must provide either name or connectionSecret"))) }) }) @@ -45,18 +53,21 @@ var _ = Describe("Binding webhook", func() { It("does not allow updates on vhost", func() { newBinding := oldBinding.DeepCopy() newBinding.Spec.Vhost = "/new-vhost" - Expect(apierrors.IsForbidden(ignoreNilWarning(newBinding.ValidateUpdate(&oldBinding)))).To(BeTrue()) + + _, err := newBinding.ValidateUpdate(rootCtx, &oldBinding, newBinding) + Expect(err).To(MatchError(ContainSubstring("updates on vhost and rabbitmqClusterReference are all forbidden"))) }) - It("does not allow updates on RabbitmqClusterReference", func() { + It("does not allow updates on RabbitmqClusterReference name", func() { newBinding := oldBinding.DeepCopy() newBinding.Spec.RabbitmqClusterReference = RabbitmqClusterReference{ Name: "new-cluster", } - Expect(apierrors.IsForbidden(ignoreNilWarning(newBinding.ValidateUpdate(&oldBinding)))).To(BeTrue()) + _, err := newBinding.ValidateUpdate(rootCtx, &oldBinding, newBinding) + Expect(err).To(MatchError(ContainSubstring("updates on vhost and rabbitmqClusterReference are all forbidden"))) }) - It("does not allow updates on rabbitmqClusterReference.connectionSecret", func() { + It("does not allow updates on rabbitmqClusterReference connectionSecret", func() { connectionScr := Binding{ ObjectMeta: metav1.ObjectMeta{ Name: "connect-test-queue", @@ -69,39 +80,45 @@ var _ = Describe("Binding webhook", func() { }, }, } - new := connectionScr.DeepCopy() - new.Spec.RabbitmqClusterReference.ConnectionSecret.Name = "new-secret" - Expect(apierrors.IsForbidden(ignoreNilWarning(new.ValidateUpdate(&connectionScr)))).To(BeTrue()) + newConnSrc := connectionScr.DeepCopy() + newConnSrc.Spec.RabbitmqClusterReference.ConnectionSecret.Name = "new-secret" + _, err := newConnSrc.ValidateUpdate(rootCtx, &connectionScr, newConnSrc) + Expect(err).To(MatchError(ContainSubstring("updates on vhost and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on source", func() { newBinding := oldBinding.DeepCopy() newBinding.Spec.Source = "updated-source" - Expect(apierrors.IsInvalid(ignoreNilWarning(newBinding.ValidateUpdate(&oldBinding)))).To(BeTrue()) + _, err := newBinding.ValidateUpdate(rootCtx, &oldBinding, newBinding) + Expect(err).To(MatchError(ContainSubstring("source cannot be updated"))) }) It("does not allow updates on destination", func() { newBinding := oldBinding.DeepCopy() newBinding.Spec.Destination = "updated-des" - Expect(apierrors.IsInvalid(ignoreNilWarning(newBinding.ValidateUpdate(&oldBinding)))).To(BeTrue()) + _, err := newBinding.ValidateUpdate(rootCtx, &oldBinding, newBinding) + Expect(err).To(MatchError(ContainSubstring("destination cannot be updated"))) }) It("does not allow updates on destination type", func() { newBinding := oldBinding.DeepCopy() newBinding.Spec.DestinationType = "exchange" - Expect(apierrors.IsInvalid(ignoreNilWarning(newBinding.ValidateUpdate(&oldBinding)))).To(BeTrue()) + _, err := newBinding.ValidateUpdate(rootCtx, &oldBinding, newBinding) + Expect(err).To(MatchError(ContainSubstring("destinationType cannot be updated"))) }) It("does not allow updates on routing key", func() { newBinding := oldBinding.DeepCopy() newBinding.Spec.RoutingKey = "not-allowed" - Expect(apierrors.IsInvalid(ignoreNilWarning(newBinding.ValidateUpdate(&oldBinding)))).To(BeTrue()) + _, err := newBinding.ValidateUpdate(rootCtx, &oldBinding, newBinding) + Expect(err).To(MatchError(ContainSubstring("routingKey cannot be updated"))) }) It("does not allow updates on binding arguments", func() { newBinding := oldBinding.DeepCopy() newBinding.Spec.Arguments = &runtime.RawExtension{Raw: []byte(`{"new":"new-value"}`)} - Expect(apierrors.IsInvalid(ignoreNilWarning(newBinding.ValidateUpdate(&oldBinding)))).To(BeTrue()) + _, err := newBinding.ValidateUpdate(rootCtx, &oldBinding, newBinding) + Expect(err).To(MatchError(ContainSubstring("arguments cannot be updated"))) }) }) }) diff --git a/api/v1beta1/exchange_webhook.go b/api/v1beta1/exchange_webhook.go index ab545f7a..94a82aab 100644 --- a/api/v1beta1/exchange_webhook.go +++ b/api/v1beta1/exchange_webhook.go @@ -1,6 +1,7 @@ package v1beta1 import ( + "context" "fmt" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -10,69 +11,79 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) -func (r *Exchange) SetupWebhookWithManager(mgr ctrl.Manager) error { +func (e *Exchange) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). - For(r). + WithValidator(e). + For(e). Complete() } // +kubebuilder:webhook:verbs=create;update,path=/validate-rabbitmq-com-v1beta1-exchange,mutating=false,failurePolicy=fail,groups=rabbitmq.com,resources=exchanges,versions=v1beta1,name=vexchange.kb.io,sideEffects=none,admissionReviewVersions=v1 -var _ webhook.Validator = &Exchange{} +var _ webhook.CustomValidator = &Exchange{} // ValidateCreate implements webhook.Validator so a webhook will be registered for the type // either rabbitmqClusterReference.name or rabbitmqClusterReference.connectionSecret must be provided but not both -func (e *Exchange) ValidateCreate() (admission.Warnings, error) { - return e.Spec.RabbitmqClusterReference.ValidateOnCreate(e.GroupResource(), e.Name) +func (e *Exchange) ValidateCreate(_ context.Context, obj runtime.Object) (warnings admission.Warnings, err error) { + ex, ok := obj.(*Exchange) + if !ok { + return nil, fmt.Errorf("expected an exchange but got a %T", obj) + } + return nil, e.Spec.RabbitmqClusterReference.validate(ex.RabbitReference()) } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type // returns error type 'forbidden' for updates that the controller chooses to disallow: exchange name/vhost/rabbitmqClusterReference // returns error type 'invalid' for updates that will be rejected by rabbitmq server: exchange types/autoDelete/durable // exchange.spec.arguments can be updated -func (e *Exchange) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - oldExchange, ok := old.(*Exchange) +func (e *Exchange) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (warnings admission.Warnings, err error) { + oldExchange, ok := oldObj.(*Exchange) + if !ok { + return nil, apierrors.NewBadRequest(fmt.Sprintf("expected an exchange but got a %T", oldObj)) + } + + newExchange, ok := newObj.(*Exchange) if !ok { - return nil, apierrors.NewBadRequest(fmt.Sprintf("expected an exchange but got a %T", old)) + return nil, apierrors.NewBadRequest(fmt.Sprintf("expected an exchange but got a %T", newObj)) } var allErrs field.ErrorList - detailMsg := "updates on name, vhost, and rabbitmqClusterReference are all forbidden" - if e.Spec.Name != oldExchange.Spec.Name { - return nil, apierrors.NewForbidden(e.GroupResource(), e.Name, + const detailMsg = "updates on name, vhost, and rabbitmqClusterReference are all forbidden" + if newExchange.Spec.Name != oldExchange.Spec.Name { + return nil, apierrors.NewForbidden(newExchange.GroupResource(), newExchange.Name, field.Forbidden(field.NewPath("spec", "name"), detailMsg)) } - if e.Spec.Vhost != oldExchange.Spec.Vhost { - return nil, apierrors.NewForbidden(e.GroupResource(), e.Name, + if newExchange.Spec.Vhost != oldExchange.Spec.Vhost { + return nil, apierrors.NewForbidden(newExchange.GroupResource(), newExchange.Name, field.Forbidden(field.NewPath("spec", "vhost"), detailMsg)) } - if !oldExchange.Spec.RabbitmqClusterReference.Matches(&e.Spec.RabbitmqClusterReference) { - return nil, apierrors.NewForbidden(e.GroupResource(), e.Name, + if !oldExchange.Spec.RabbitmqClusterReference.Matches(&newExchange.Spec.RabbitmqClusterReference) { + return nil, apierrors.NewForbidden(newExchange.GroupResource(), newExchange.Name, field.Forbidden(field.NewPath("spec", "rabbitmqClusterReference"), detailMsg)) } - if e.Spec.Type != oldExchange.Spec.Type { + if newExchange.Spec.Type != oldExchange.Spec.Type { allErrs = append(allErrs, field.Invalid( field.NewPath("spec", "type"), - e.Spec.Type, + newExchange.Spec.Type, "exchange type cannot be updated", )) } - if e.Spec.AutoDelete != oldExchange.Spec.AutoDelete { + if newExchange.Spec.AutoDelete != oldExchange.Spec.AutoDelete { allErrs = append(allErrs, field.Invalid( field.NewPath("spec", "autoDelete"), - e.Spec.AutoDelete, + newExchange.Spec.AutoDelete, "autoDelete cannot be updated", )) } - if e.Spec.Durable != oldExchange.Spec.Durable { + if newExchange.Spec.Durable != oldExchange.Spec.Durable { allErrs = append(allErrs, field.Invalid( field.NewPath("spec", "durable"), - e.Spec.AutoDelete, + newExchange.Spec.AutoDelete, "durable cannot be updated", )) } @@ -81,9 +92,9 @@ func (e *Exchange) ValidateUpdate(old runtime.Object) (admission.Warnings, error return nil, nil } - return nil, apierrors.NewInvalid(GroupVersion.WithKind("Exchange").GroupKind(), e.Name, allErrs) + return nil, allErrs.ToAggregate() } -func (e *Exchange) ValidateDelete() (admission.Warnings, error) { +func (e *Exchange) ValidateDelete(_ context.Context, _ runtime.Object) (warnings admission.Warnings, err error) { return nil, nil } diff --git a/api/v1beta1/exchange_webhook_test.go b/api/v1beta1/exchange_webhook_test.go index 047fcffe..ae606473 100644 --- a/api/v1beta1/exchange_webhook_test.go +++ b/api/v1beta1/exchange_webhook_test.go @@ -1,44 +1,49 @@ package v1beta1 import ( + "context" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) var _ = Describe("exchange webhook", func() { - var exchange = Exchange{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-exchange", - }, - Spec: ExchangeSpec{ - Name: "test", - Vhost: "/test", - Type: "fanout", - Durable: false, - AutoDelete: true, - RabbitmqClusterReference: RabbitmqClusterReference{ - Name: "some-cluster", + var ( + exchange = Exchange{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-exchange", }, - }, - } + Spec: ExchangeSpec{ + Name: "test", + Vhost: "/test", + Type: "fanout", + Durable: false, + AutoDelete: true, + RabbitmqClusterReference: RabbitmqClusterReference{ + Name: "some-cluster", + }, + }, + } + rootCtx = context.Background() + ) Context("ValidateCreate", func() { It("does not allow both spec.rabbitmqClusterReference.name and spec.rabbitmqClusterReference.connectionSecret be configured", func() { notAllowed := exchange.DeepCopy() notAllowed.Spec.RabbitmqClusterReference.ConnectionSecret = &corev1.LocalObjectReference{Name: "some-secret"} - Expect(apierrors.IsForbidden(ignoreNilWarning(notAllowed.ValidateCreate()))).To(BeTrue()) + _, err := notAllowed.ValidateCreate(rootCtx, notAllowed) + Expect(err).To(MatchError(ContainSubstring("invalid RabbitmqClusterReference: do not provide both name and connectionSecret"))) }) It("spec.rabbitmqClusterReference.name and spec.rabbitmqClusterReference.connectionSecret cannot both be empty", func() { notAllowed := exchange.DeepCopy() notAllowed.Spec.RabbitmqClusterReference.Name = "" notAllowed.Spec.RabbitmqClusterReference.ConnectionSecret = nil - Expect(apierrors.IsForbidden(ignoreNilWarning(notAllowed.ValidateCreate()))).To(BeTrue()) + _, err := notAllowed.ValidateCreate(rootCtx, notAllowed) + Expect(err).To(MatchError(ContainSubstring("invalid RabbitmqClusterReference: must provide either name or connectionSecret"))) }) }) @@ -46,13 +51,15 @@ var _ = Describe("exchange webhook", func() { It("does not allow updates on exchange name", func() { newExchange := exchange.DeepCopy() newExchange.Spec.Name = "new-name" - Expect(apierrors.IsForbidden(ignoreNilWarning(newExchange.ValidateUpdate(&exchange)))).To(BeTrue()) + _, err := newExchange.ValidateUpdate(rootCtx, &exchange, newExchange) + Expect(err).To(MatchError(ContainSubstring("updates on name, vhost, and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on vhost", func() { newExchange := exchange.DeepCopy() newExchange.Spec.Vhost = "/a-new-vhost" - Expect(apierrors.IsForbidden(ignoreNilWarning(newExchange.ValidateUpdate(&exchange)))).To(BeTrue()) + _, err := newExchange.ValidateUpdate(rootCtx, &exchange, newExchange) + Expect(err).To(MatchError(ContainSubstring("updates on name, vhost, and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on RabbitmqClusterReference", func() { @@ -60,7 +67,8 @@ var _ = Describe("exchange webhook", func() { newExchange.Spec.RabbitmqClusterReference = RabbitmqClusterReference{ Name: "new-cluster", } - Expect(apierrors.IsForbidden(ignoreNilWarning(newExchange.ValidateUpdate(&exchange)))).To(BeTrue()) + _, err := newExchange.ValidateUpdate(rootCtx, &exchange, newExchange) + Expect(err).To(MatchError(ContainSubstring("updates on name, vhost, and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on rabbitmqClusterReference.connectionSecret", func() { @@ -79,33 +87,38 @@ var _ = Describe("exchange webhook", func() { }, }, } - new := connectionScr.DeepCopy() - new.Spec.RabbitmqClusterReference.ConnectionSecret.Name = "new-secret" - Expect(apierrors.IsForbidden(ignoreNilWarning(new.ValidateUpdate(&connectionScr)))).To(BeTrue()) + newExchange := connectionScr.DeepCopy() + newExchange.Spec.RabbitmqClusterReference.ConnectionSecret.Name = "new-secret" + _, err := newExchange.ValidateUpdate(rootCtx, &connectionScr, newExchange) + Expect(err).To(MatchError(ContainSubstring("updates on name, vhost, and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on exchange type", func() { newExchange := exchange.DeepCopy() newExchange.Spec.Type = "direct" - Expect(apierrors.IsInvalid(ignoreNilWarning(newExchange.ValidateUpdate(&exchange)))).To(BeTrue()) + _, err := newExchange.ValidateUpdate(rootCtx, &exchange, newExchange) + Expect(err).To(MatchError(ContainSubstring("exchange type cannot be updated"))) }) It("does not allow updates on durable", func() { newExchange := exchange.DeepCopy() newExchange.Spec.Durable = true - Expect(apierrors.IsInvalid(ignoreNilWarning(newExchange.ValidateUpdate(&exchange)))).To(BeTrue()) + _, err := newExchange.ValidateUpdate(rootCtx, &exchange, newExchange) + Expect(err).To(MatchError(ContainSubstring("durable cannot be updated"))) }) It("does not allow updates on autoDelete", func() { newExchange := exchange.DeepCopy() newExchange.Spec.AutoDelete = false - Expect(apierrors.IsInvalid(ignoreNilWarning(newExchange.ValidateUpdate(&exchange)))).To(BeTrue()) + _, err := newExchange.ValidateUpdate(rootCtx, &exchange, newExchange) + Expect(err).To(MatchError(ContainSubstring("autoDelete cannot be updated"))) }) It("allows updates on arguments", func() { newExchange := exchange.DeepCopy() newExchange.Spec.Arguments = &runtime.RawExtension{Raw: []byte(`{"new":"new-value"}`)} - Expect(ignoreNilWarning(newExchange.ValidateUpdate(&exchange))).To(Succeed()) + _, err := newExchange.ValidateUpdate(rootCtx, &exchange, newExchange) + Expect(err).To(Succeed()) }) }) }) diff --git a/api/v1beta1/federation_webhook.go b/api/v1beta1/federation_webhook.go index 9c4b1fd4..b9af8b87 100644 --- a/api/v1beta1/federation_webhook.go +++ b/api/v1beta1/federation_webhook.go @@ -1,53 +1,68 @@ package v1beta1 import ( + "context" "fmt" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation/field" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/webhook" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) func (f *Federation) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). + WithValidator(f). For(f). Complete() } +var _ webhook.CustomValidator = &Federation{} + // +kubebuilder:webhook:verbs=create;update,path=/validate-rabbitmq-com-v1beta1-federation,mutating=false,failurePolicy=fail,groups=rabbitmq.com,resources=federations,versions=v1beta1,name=vfederation.kb.io,sideEffects=none,admissionReviewVersions=v1 // ValidateCreate implements webhook.Validator so a webhook will be registered for the type // either rabbitmqClusterReference.name or rabbitmqClusterReference.connectionSecret must be provided but not both -func (f *Federation) ValidateCreate() (admission.Warnings, error) { - return f.Spec.RabbitmqClusterReference.ValidateOnCreate(f.GroupResource(), f.Name) +func (f *Federation) ValidateCreate(_ context.Context, obj runtime.Object) (warnings admission.Warnings, err error) { + //TODO implement me + fed, ok := obj.(*Federation) + if !ok { + return nil, fmt.Errorf("expected a RabbitMQ Federation, but got %T", obj) + } + return nil, f.Spec.RabbitmqClusterReference.validate(fed.RabbitReference()) } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (f *Federation) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - oldFederation, ok := old.(*Federation) +func (f *Federation) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (warnings admission.Warnings, err error) { + oldFederation, ok := oldObj.(*Federation) + if !ok { + return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a federation but got a %T", oldObj)) + } + + newFederation, ok := newObj.(*Federation) if !ok { - return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a federation but got a %T", old)) + return nil, fmt.Errorf("expected a RabbitMQ Federation, but got %T", newObj) } - detailMsg := "updates on name, vhost and rabbitmqClusterReference are all forbidden" - if f.Spec.Name != oldFederation.Spec.Name { - return nil, apierrors.NewForbidden(f.GroupResource(), f.Name, + const detailMsg = "updates on name, vhost and rabbitmqClusterReference are all forbidden" + if newFederation.Spec.Name != oldFederation.Spec.Name { + return nil, apierrors.NewForbidden(newFederation.GroupResource(), newFederation.Name, field.Forbidden(field.NewPath("spec", "name"), detailMsg)) } - if f.Spec.Vhost != oldFederation.Spec.Vhost { - return nil, apierrors.NewForbidden(f.GroupResource(), f.Name, + if newFederation.Spec.Vhost != oldFederation.Spec.Vhost { + return nil, apierrors.NewForbidden(newFederation.GroupResource(), newFederation.Name, field.Forbidden(field.NewPath("spec", "vhost"), detailMsg)) } - if !oldFederation.Spec.RabbitmqClusterReference.Matches(&f.Spec.RabbitmqClusterReference) { - return nil, apierrors.NewForbidden(f.GroupResource(), f.Name, + if !oldFederation.Spec.RabbitmqClusterReference.Matches(&newFederation.Spec.RabbitmqClusterReference) { + return nil, apierrors.NewForbidden(newFederation.GroupResource(), newFederation.Name, field.Forbidden(field.NewPath("spec", "rabbitmqClusterReference"), detailMsg)) } return nil, nil } -func (f *Federation) ValidateDelete() (admission.Warnings, error) { +func (f *Federation) ValidateDelete(_ context.Context, _ runtime.Object) (warnings admission.Warnings, err error) { return nil, nil } diff --git a/api/v1beta1/federation_webhook_test.go b/api/v1beta1/federation_webhook_test.go index 654c6f4a..766b1b09 100644 --- a/api/v1beta1/federation_webhook_test.go +++ b/api/v1beta1/federation_webhook_test.go @@ -1,49 +1,55 @@ package v1beta1 import ( + "context" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) var _ = Describe("federation webhook", func() { - var federation = Federation{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - }, - Spec: FederationSpec{ - Name: "test-upstream", - Vhost: "/a-vhost", - UriSecret: &corev1.LocalObjectReference{ - Name: "a-secret", + var ( + federation = Federation{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", }, - Expires: 1000, - MessageTTL: 1000, - MaxHops: 100, - PrefetchCount: 50, - ReconnectDelay: 10, - TrustUserId: true, - Exchange: "an-exchange", - AckMode: "no-ack", - RabbitmqClusterReference: RabbitmqClusterReference{ - Name: "a-cluster", + Spec: FederationSpec{ + Name: "test-upstream", + Vhost: "/a-vhost", + UriSecret: &corev1.LocalObjectReference{ + Name: "a-secret", + }, + Expires: 1000, + MessageTTL: 1000, + MaxHops: 100, + PrefetchCount: 50, + ReconnectDelay: 10, + TrustUserId: true, + Exchange: "an-exchange", + AckMode: "no-ack", + RabbitmqClusterReference: RabbitmqClusterReference{ + Name: "a-cluster", + }, }, - }, - } + } + rootCtx = context.Background() + ) + Context("ValidateCreate", func() { It("does not allow both spec.rabbitmqClusterReference.name and spec.rabbitmqClusterReference.connectionSecret be configured", func() { notAllowed := federation.DeepCopy() notAllowed.Spec.RabbitmqClusterReference.ConnectionSecret = &corev1.LocalObjectReference{Name: "some-secret"} - Expect(apierrors.IsForbidden(ignoreNilWarning(notAllowed.ValidateCreate()))).To(BeTrue()) + _, err := notAllowed.ValidateCreate(rootCtx, notAllowed) + Expect(err).To(MatchError(ContainSubstring("invalid RabbitmqClusterReference: do not provide both name and connectionSecret"))) }) It("spec.rabbitmqClusterReference.name and spec.rabbitmqClusterReference.connectionSecret cannot both be empty", func() { notAllowed := federation.DeepCopy() notAllowed.Spec.RabbitmqClusterReference.Name = "" notAllowed.Spec.RabbitmqClusterReference.ConnectionSecret = nil - Expect(apierrors.IsForbidden(ignoreNilWarning(notAllowed.ValidateCreate()))).To(BeTrue()) + _, err := notAllowed.ValidateCreate(rootCtx, notAllowed) + Expect(err).To(MatchError(ContainSubstring("invalid RabbitmqClusterReference: must provide either name or connectionSecret"))) }) }) @@ -51,13 +57,15 @@ var _ = Describe("federation webhook", func() { It("does not allow updates on name", func() { newFederation := federation.DeepCopy() newFederation.Spec.Name = "new-upstream" - Expect(apierrors.IsForbidden(ignoreNilWarning(newFederation.ValidateUpdate(&federation)))).To(BeTrue()) + _, err := newFederation.ValidateUpdate(rootCtx, &federation, newFederation) + Expect(err).To(MatchError(ContainSubstring("updates on name, vhost and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on vhost", func() { newFederation := federation.DeepCopy() newFederation.Spec.Vhost = "new-vhost" - Expect(apierrors.IsForbidden(ignoreNilWarning(newFederation.ValidateUpdate(&federation)))).To(BeTrue()) + _, err := newFederation.ValidateUpdate(rootCtx, &federation, newFederation) + Expect(err).To(MatchError(ContainSubstring("updates on name, vhost and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on RabbitmqClusterReference", func() { @@ -65,7 +73,8 @@ var _ = Describe("federation webhook", func() { newFederation.Spec.RabbitmqClusterReference = RabbitmqClusterReference{ Name: "new-cluster", } - Expect(apierrors.IsForbidden(ignoreNilWarning(newFederation.ValidateUpdate(&federation)))).To(BeTrue()) + _, err := newFederation.ValidateUpdate(rootCtx, &federation, newFederation) + Expect(err).To(MatchError(ContainSubstring("updates on name, vhost and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on rabbitmqClusterReference.connectionSecret", func() { @@ -86,9 +95,10 @@ var _ = Describe("federation webhook", func() { }, }, } - new := connectionScr.DeepCopy() - new.Spec.RabbitmqClusterReference.ConnectionSecret.Name = "new-secret" - Expect(apierrors.IsForbidden(ignoreNilWarning(new.ValidateUpdate(&connectionScr)))).To(BeTrue()) + newFederation := connectionScr.DeepCopy() + newFederation.Spec.RabbitmqClusterReference.ConnectionSecret.Name = "new-secret" + _, err := newFederation.ValidateUpdate(rootCtx, &connectionScr, newFederation) + Expect(err).To(MatchError(ContainSubstring("updates on name, vhost and rabbitmqClusterReference are all forbidden"))) }) It("allows updates on federation configurations", func() { @@ -102,7 +112,8 @@ var _ = Describe("federation webhook", func() { newFederation.Spec.TrustUserId = false newFederation.Spec.Exchange = "new-exchange" newFederation.Spec.AckMode = "no-ack" - Expect(ignoreNilWarning(newFederation.ValidateUpdate(&federation))).To(Succeed()) + _, err := newFederation.ValidateUpdate(rootCtx, &federation, newFederation) + Expect(err).To(Succeed()) }) }) }) diff --git a/api/v1beta1/operatorpolicy_webhook.go b/api/v1beta1/operatorpolicy_webhook.go index 42a1009d..f6d77a61 100644 --- a/api/v1beta1/operatorpolicy_webhook.go +++ b/api/v1beta1/operatorpolicy_webhook.go @@ -1,8 +1,8 @@ package v1beta1 import ( + "context" "fmt" - apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation/field" @@ -13,45 +13,55 @@ import ( func (p *OperatorPolicy) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). + WithValidator(p). For(p). Complete() } // +kubebuilder:webhook:verbs=create;update,path=/validate-rabbitmq-com-v1beta1-operatorpolicy,mutating=false,failurePolicy=fail,groups=rabbitmq.com,resources=operatorpolicies,versions=v1beta1,name=voperatorpolicy.kb.io,sideEffects=none,admissionReviewVersions=v1 -var _ webhook.Validator = &OperatorPolicy{} +var _ webhook.CustomValidator = &OperatorPolicy{} // ValidateCreate implements webhook.Validator so a webhook will be registered for the type // either rabbitmqClusterReference.name or rabbitmqClusterReference.connectionSecret must be provided but not both -func (p *OperatorPolicy) ValidateCreate() (admission.Warnings, error) { - return p.Spec.RabbitmqClusterReference.ValidateOnCreate(p.GroupResource(), p.Name) +func (p *OperatorPolicy) ValidateCreate(_ context.Context, obj runtime.Object) (warnings admission.Warnings, err error) { + op, ok := obj.(*OperatorPolicy) + if !ok { + return nil, fmt.Errorf("expected an operator policy but got %T", obj) + } + return nil, p.Spec.RabbitmqClusterReference.validate(op.RabbitReference()) } // ValidateUpdate returns error type 'forbidden' for updates on operator policy name, vhost and rabbitmqClusterReference -func (p *OperatorPolicy) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - oldOperatorPolicy, ok := old.(*OperatorPolicy) +func (p *OperatorPolicy) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (warnings admission.Warnings, err error) { + oldOperatorPolicy, ok := oldObj.(*OperatorPolicy) + if !ok { + return nil, apierrors.NewBadRequest(fmt.Sprintf("expected an operator policy but got a %T", oldObj)) + } + + newOperatorPolicy, ok := newObj.(*OperatorPolicy) if !ok { - return nil, apierrors.NewBadRequest(fmt.Sprintf("expected an operator policy but got a %T", old)) + return nil, apierrors.NewBadRequest(fmt.Sprintf("expected an operator policy but got a %T", newObj)) } - detailMsg := "updates on name, vhost and rabbitmqClusterReference are all forbidden" - if p.Spec.Name != oldOperatorPolicy.Spec.Name { - return nil, apierrors.NewForbidden(p.GroupResource(), p.Name, + const detailMsg = "updates on name, vhost and rabbitmqClusterReference are all forbidden" + if newOperatorPolicy.Spec.Name != oldOperatorPolicy.Spec.Name { + return nil, apierrors.NewForbidden(newOperatorPolicy.GroupResource(), newOperatorPolicy.Name, field.Forbidden(field.NewPath("spec", "name"), detailMsg)) } - if p.Spec.Vhost != oldOperatorPolicy.Spec.Vhost { - return nil, apierrors.NewForbidden(p.GroupResource(), p.Name, + if newOperatorPolicy.Spec.Vhost != oldOperatorPolicy.Spec.Vhost { + return nil, apierrors.NewForbidden(newOperatorPolicy.GroupResource(), newOperatorPolicy.Name, field.Forbidden(field.NewPath("spec", "vhost"), detailMsg)) } - if !oldOperatorPolicy.Spec.RabbitmqClusterReference.Matches(&p.Spec.RabbitmqClusterReference) { - return nil, apierrors.NewForbidden(p.GroupResource(), p.Name, + if !oldOperatorPolicy.Spec.RabbitmqClusterReference.Matches(&newOperatorPolicy.Spec.RabbitmqClusterReference) { + return nil, apierrors.NewForbidden(newOperatorPolicy.GroupResource(), newOperatorPolicy.Name, field.Forbidden(field.NewPath("spec", "rabbitmqClusterReference"), detailMsg)) } return nil, nil } -func (p *OperatorPolicy) ValidateDelete() (admission.Warnings, error) { +func (p *OperatorPolicy) ValidateDelete(_ context.Context, _ runtime.Object) (warnings admission.Warnings, err error) { return nil, nil } diff --git a/api/v1beta1/operatorpolicy_webhook_test.go b/api/v1beta1/operatorpolicy_webhook_test.go index 04809d15..cab822a7 100644 --- a/api/v1beta1/operatorpolicy_webhook_test.go +++ b/api/v1beta1/operatorpolicy_webhook_test.go @@ -1,43 +1,48 @@ package v1beta1 import ( + "context" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) var _ = Describe("policy webhook", func() { - var policy = OperatorPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - }, - Spec: OperatorPolicySpec{ - Name: "test", - Vhost: "/test", - Pattern: "a-pattern", - ApplyTo: "queues", - Priority: 0, - RabbitmqClusterReference: RabbitmqClusterReference{ - Name: "a-cluster", + var ( + policy = OperatorPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", }, - }, - } + Spec: OperatorPolicySpec{ + Name: "test", + Vhost: "/test", + Pattern: "a-pattern", + ApplyTo: "queues", + Priority: 0, + RabbitmqClusterReference: RabbitmqClusterReference{ + Name: "a-cluster", + }, + }, + } + rootCtx = context.Background() + ) Context("ValidateCreate", func() { It("does not allow both spec.rabbitmqClusterReference.name and spec.rabbitmqClusterReference.connectionSecret be configured", func() { notAllowed := policy.DeepCopy() notAllowed.Spec.RabbitmqClusterReference.ConnectionSecret = &corev1.LocalObjectReference{Name: "some-secret"} - Expect(apierrors.IsForbidden(ignoreNilWarning(notAllowed.ValidateCreate()))).To(BeTrue()) + _, err := notAllowed.ValidateCreate(rootCtx, notAllowed) + Expect(err).To(MatchError(ContainSubstring("invalid RabbitmqClusterReference: do not provide both name and connectionSecret"))) }) It("spec.rabbitmqClusterReference.name and spec.rabbitmqClusterReference.connectionSecret cannot both be empty", func() { notAllowed := policy.DeepCopy() notAllowed.Spec.RabbitmqClusterReference.Name = "" notAllowed.Spec.RabbitmqClusterReference.ConnectionSecret = nil - Expect(apierrors.IsForbidden(ignoreNilWarning(notAllowed.ValidateCreate()))).To(BeTrue()) + _, err := notAllowed.ValidateCreate(rootCtx, notAllowed) + Expect(err).To(MatchError(ContainSubstring("invalid RabbitmqClusterReference: must provide either name or connectionSecret"))) }) }) @@ -45,13 +50,15 @@ var _ = Describe("policy webhook", func() { It("does not allow updates on operator policy name", func() { newPolicy := policy.DeepCopy() newPolicy.Spec.Name = "new-name" - Expect(apierrors.IsForbidden(ignoreNilWarning(newPolicy.ValidateUpdate(&policy)))).To(BeTrue()) + _, err := newPolicy.ValidateUpdate(rootCtx, &policy, newPolicy) + Expect(err).To(MatchError(ContainSubstring("updates on name, vhost and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on vhost", func() { newPolicy := policy.DeepCopy() newPolicy.Spec.Vhost = "new-vhost" - Expect(apierrors.IsForbidden(ignoreNilWarning(newPolicy.ValidateUpdate(&policy)))).To(BeTrue()) + _, err := newPolicy.ValidateUpdate(rootCtx, &policy, newPolicy) + Expect(err).To(MatchError(ContainSubstring("updates on name, vhost and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on RabbitmqClusterReference", func() { @@ -59,7 +66,8 @@ var _ = Describe("policy webhook", func() { newPolicy.Spec.RabbitmqClusterReference = RabbitmqClusterReference{ Name: "new-cluster", } - Expect(apierrors.IsForbidden(ignoreNilWarning(newPolicy.ValidateUpdate(&policy)))).To(BeTrue()) + _, err := newPolicy.ValidateUpdate(rootCtx, &policy, newPolicy) + Expect(err).To(MatchError(ContainSubstring("updates on name, vhost and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on rabbitmqClusterReference.connectionSecret", func() { @@ -80,33 +88,38 @@ var _ = Describe("policy webhook", func() { }, }, } - new := connectionScr.DeepCopy() - new.Spec.RabbitmqClusterReference.ConnectionSecret.Name = "new-secret" - Expect(apierrors.IsForbidden(ignoreNilWarning(new.ValidateUpdate(&connectionScr)))).To(BeTrue()) + newOperatorPolicy := connectionScr.DeepCopy() + newOperatorPolicy.Spec.RabbitmqClusterReference.ConnectionSecret.Name = "new-secret" + _, err := newOperatorPolicy.ValidateUpdate(rootCtx, &connectionScr, newOperatorPolicy) + Expect(err).To(MatchError(ContainSubstring("updates on name, vhost and rabbitmqClusterReference are all forbidden"))) }) It("allows updates on operator policy.spec.pattern", func() { newPolicy := policy.DeepCopy() newPolicy.Spec.Pattern = "new-pattern" - Expect(ignoreNilWarning(newPolicy.ValidateUpdate(&policy))).To(Succeed()) + _, err := newPolicy.ValidateUpdate(rootCtx, &policy, newPolicy) + Expect(err).To(Succeed()) }) It("allows updates on operator policy.spec.applyTo", func() { newPolicy := policy.DeepCopy() newPolicy.Spec.ApplyTo = "queues" - Expect(ignoreNilWarning(newPolicy.ValidateUpdate(&policy))).To(Succeed()) + _, err := newPolicy.ValidateUpdate(rootCtx, &policy, newPolicy) + Expect(err).To(Succeed()) }) It("allows updates on operator policy.spec.priority", func() { newPolicy := policy.DeepCopy() newPolicy.Spec.Priority = 1000 - Expect(ignoreNilWarning(newPolicy.ValidateUpdate(&policy))).To(Succeed()) + _, err := newPolicy.ValidateUpdate(rootCtx, &policy, newPolicy) + Expect(err).To(Succeed()) }) It("allows updates on operator policy.spec.definition", func() { newPolicy := policy.DeepCopy() newPolicy.Spec.Definition = &runtime.RawExtension{Raw: []byte(`{"key":"new-definition-value"}`)} - Expect(ignoreNilWarning(newPolicy.ValidateUpdate(&policy))).To(Succeed()) + _, err := newPolicy.ValidateUpdate(rootCtx, &policy, newPolicy) + Expect(err).To(Succeed()) }) }) }) diff --git a/api/v1beta1/permission_webhook.go b/api/v1beta1/permission_webhook.go index bb26de08..2b63675e 100644 --- a/api/v1beta1/permission_webhook.go +++ b/api/v1beta1/permission_webhook.go @@ -1,6 +1,7 @@ package v1beta1 import ( + "context" "fmt" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -13,78 +14,90 @@ import ( func (p *Permission) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). + WithValidator(p). For(p). Complete() } // +kubebuilder:webhook:verbs=create;update,path=/validate-rabbitmq-com-v1beta1-permission,mutating=false,failurePolicy=fail,groups=rabbitmq.com,resources=permissions,versions=v1beta1,name=vpermission.kb.io,sideEffects=none,admissionReviewVersions=v1 -var _ webhook.Validator = &Permission{} +var _ webhook.CustomValidator = &Permission{} // ValidateCreate checks if only one of spec.user and spec.userReference is specified // either rabbitmqClusterReference.name or rabbitmqClusterReference.connectionSecret must be provided but not both -func (p *Permission) ValidateCreate() (admission.Warnings, error) { - var errorList field.ErrorList - if p.Spec.User == "" && p.Spec.UserReference == nil { - errorList = append(errorList, field.Required(field.NewPath("spec", "user and userReference"), - "must specify either spec.user or spec.userReference")) - return nil, apierrors.NewInvalid(GroupVersion.WithKind("Permission").GroupKind(), p.Name, errorList) +func (p *Permission) ValidateCreate(_ context.Context, obj runtime.Object) (warnings admission.Warnings, err error) { + pe, ok := obj.(*Permission) + if !ok { + return nil, fmt.Errorf("expected a RabbitMQ permission but got a %T", obj) } - if p.Spec.User != "" && p.Spec.UserReference != nil { - errorList = append(errorList, field.Required(field.NewPath("spec", "user and userReference"), - "cannot specify spec.user and spec.userReference at the same time")) - return nil, apierrors.NewInvalid(GroupVersion.WithKind("Permission").GroupKind(), p.Name, errorList) + if pe.Spec.User == "" && pe.Spec.UserReference == nil { + return nil, field.Required(field.NewPath("spec", "user and userReference"), + "must specify either spec.user or spec.userReference") + } + + if pe.Spec.User != "" && pe.Spec.UserReference != nil { + return nil, field.Required(field.NewPath("spec", "user and userReference"), + "cannot specify spec.user and spec.userReference at the same time") } - return p.Spec.RabbitmqClusterReference.ValidateOnCreate(p.GroupResource(), p.Name) + return nil, pe.Spec.RabbitmqClusterReference.validate(pe.RabbitReference()) } // ValidateUpdate do not allow updates on spec.vhost, spec.user, spec.userReference, and spec.rabbitmqClusterReference // updates on spec.permissions are allowed // only one of spec.user and spec.userReference can be specified -func (p *Permission) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - oldPermission, ok := old.(*Permission) +func (p *Permission) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (warnings admission.Warnings, err error) { + oldPermission, ok := oldObj.(*Permission) if !ok { - return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a permission but got a %T", old)) + return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a permission but got a %T", oldObj)) + } + + newPermission, ok := newObj.(*Permission) + if !ok { + return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a permission but got a %T", newObj)) } var errorList field.ErrorList - if p.Spec.User == "" && p.Spec.UserReference == nil { + if newPermission.Spec.User == "" && newPermission.Spec.UserReference == nil { errorList = append(errorList, field.Required(field.NewPath("spec", "user and userReference"), "must specify either spec.user or spec.userReference")) - return nil, apierrors.NewInvalid(GroupVersion.WithKind("Permission").GroupKind(), p.Name, errorList) + return nil, apierrors.NewInvalid(GroupVersion.WithKind("Permission").GroupKind(), newPermission.Name, errorList) } - if p.Spec.User != "" && p.Spec.UserReference != nil { + if newPermission.Spec.User != "" && newPermission.Spec.UserReference != nil { errorList = append(errorList, field.Required(field.NewPath("spec", "user and userReference"), "cannot specify spec.user and spec.userReference at the same time")) - return nil, apierrors.NewInvalid(GroupVersion.WithKind("Permission").GroupKind(), p.Name, errorList) + return nil, apierrors.NewInvalid(GroupVersion.WithKind("Permission").GroupKind(), newPermission.Name, errorList) } - detailMsg := "updates on user, userReference, vhost and rabbitmqClusterReference are all forbidden" - if p.Spec.User != oldPermission.Spec.User { - return nil, apierrors.NewForbidden(p.GroupResource(), p.Name, + const detailMsg = "updates on user, userReference, vhost and rabbitmqClusterReference are all forbidden" + if newPermission.Spec.User != oldPermission.Spec.User { + return nil, apierrors.NewForbidden(newPermission.GroupResource(), newPermission.Name, field.Forbidden(field.NewPath("spec", "user"), detailMsg)) } - if userReferenceUpdated(p.Spec.UserReference, oldPermission.Spec.UserReference) { - return nil, apierrors.NewForbidden(p.GroupResource(), p.Name, + if userReferenceUpdated(newPermission.Spec.UserReference, oldPermission.Spec.UserReference) { + return nil, apierrors.NewForbidden(newPermission.GroupResource(), newPermission.Name, field.Forbidden(field.NewPath("spec", "userReference"), detailMsg)) } - if p.Spec.Vhost != oldPermission.Spec.Vhost { - return nil, apierrors.NewForbidden(p.GroupResource(), p.Name, + if newPermission.Spec.Vhost != oldPermission.Spec.Vhost { + return nil, apierrors.NewForbidden(newPermission.GroupResource(), newPermission.Name, field.Forbidden(field.NewPath("spec", "vhost"), detailMsg)) } - if !oldPermission.Spec.RabbitmqClusterReference.Matches(&p.Spec.RabbitmqClusterReference) { - return nil, apierrors.NewForbidden(p.GroupResource(), p.Name, + if !oldPermission.Spec.RabbitmqClusterReference.Matches(&newPermission.Spec.RabbitmqClusterReference) { + return nil, apierrors.NewForbidden(newPermission.GroupResource(), newPermission.Name, field.Forbidden(field.NewPath("spec", "rabbitmqClusterReference"), detailMsg)) } return nil, nil } +func (p *Permission) ValidateDelete(_ context.Context, _ runtime.Object) (warnings admission.Warnings, err error) { + return nil, nil +} + // returns true if userReference, which is a pointer to corev1.LocalObjectReference, has changed func userReferenceUpdated(new, old *corev1.LocalObjectReference) bool { if new == nil && old == nil { @@ -99,8 +112,3 @@ func userReferenceUpdated(new, old *corev1.LocalObjectReference) bool { } return false } - -// ValidateDelete no validation on delete -func (p *Permission) ValidateDelete() (admission.Warnings, error) { - return nil, nil -} diff --git a/api/v1beta1/permission_webhook_test.go b/api/v1beta1/permission_webhook_test.go index 1a5152ac..a0983852 100644 --- a/api/v1beta1/permission_webhook_test.go +++ b/api/v1beta1/permission_webhook_test.go @@ -1,57 +1,64 @@ package v1beta1 import ( + "context" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) var _ = Describe("permission webhook", func() { - var permission = Permission{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - }, - Spec: PermissionSpec{ - User: "test-user", - Vhost: "/a-vhost", - Permissions: VhostPermissions{ - Configure: ".*", - Read: ".*", - Write: ".*", + var ( + permission = Permission{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", }, - RabbitmqClusterReference: RabbitmqClusterReference{ - Name: "a-cluster", + Spec: PermissionSpec{ + User: "test-user", + Vhost: "/a-vhost", + Permissions: VhostPermissions{ + Configure: ".*", + Read: ".*", + Write: ".*", + }, + RabbitmqClusterReference: RabbitmqClusterReference{ + Name: "a-cluster", + }, }, - }, - } + } + rootCtx = context.Background() + ) Context("ValidateCreate", func() { It("does not allow user and userReference to be specified at the same time", func() { invalidPermission := permission.DeepCopy() invalidPermission.Spec.UserReference = &corev1.LocalObjectReference{Name: "invalid"} invalidPermission.Spec.User = "test-user" - Expect(apierrors.IsInvalid(ignoreNilWarning(invalidPermission.ValidateCreate()))).To(BeTrue()) + _, err := invalidPermission.ValidateCreate(rootCtx, invalidPermission) + Expect(err).To(MatchError(ContainSubstring("cannot specify spec.user and spec.userReference at the same time"))) }) It("does not allow both user and userReference to be unset", func() { invalidPermission := permission.DeepCopy() invalidPermission.Spec.UserReference = nil invalidPermission.Spec.User = "" - Expect(apierrors.IsInvalid(ignoreNilWarning(invalidPermission.ValidateCreate()))).To(BeTrue()) + _, err := invalidPermission.ValidateCreate(rootCtx, invalidPermission) + Expect(err).To(MatchError(ContainSubstring("must specify either spec.user or spec.userReference"))) }) It("does not allow both spec.rabbitmqClusterReference.name and spec.rabbitmqClusterReference.connectionSecret be configured", func() { notAllowed := permission.DeepCopy() notAllowed.Spec.RabbitmqClusterReference.ConnectionSecret = &corev1.LocalObjectReference{Name: "some-secret"} - Expect(apierrors.IsForbidden(ignoreNilWarning(notAllowed.ValidateCreate()))).To(BeTrue()) + _, err := notAllowed.ValidateCreate(rootCtx, notAllowed) + Expect(err).To(MatchError(ContainSubstring("invalid RabbitmqClusterReference: do not provide both name and connectionSecret"))) }) It("spec.rabbitmqClusterReference.name and spec.rabbitmqClusterReference.connectionSecret cannot both be empty", func() { notAllowed := permission.DeepCopy() notAllowed.Spec.RabbitmqClusterReference.Name = "" notAllowed.Spec.RabbitmqClusterReference.ConnectionSecret = nil - Expect(apierrors.IsForbidden(ignoreNilWarning(notAllowed.ValidateCreate()))).To(BeTrue()) + _, err := notAllowed.ValidateCreate(rootCtx, notAllowed) + Expect(err).To(MatchError(ContainSubstring("invalid RabbitmqClusterReference: must provide either name or connectionSecret"))) }) }) @@ -59,7 +66,8 @@ var _ = Describe("permission webhook", func() { It("does not allow updates on user", func() { newPermission := permission.DeepCopy() newPermission.Spec.User = "new-user" - Expect(apierrors.IsForbidden(ignoreNilWarning(newPermission.ValidateUpdate(&permission)))).To(BeTrue()) + _, err := newPermission.ValidateUpdate(rootCtx, &permission, newPermission) + Expect(err).To(MatchError(ContainSubstring("updates on user, userReference, vhost and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on userReference", func() { @@ -68,13 +76,15 @@ var _ = Describe("permission webhook", func() { permissionWithUserRef.Spec.UserReference = &corev1.LocalObjectReference{Name: "a-user"} newPermission := permissionWithUserRef.DeepCopy() newPermission.Spec.UserReference = &corev1.LocalObjectReference{Name: "a-new-user"} - Expect(apierrors.IsForbidden(ignoreNilWarning(newPermission.ValidateUpdate(permissionWithUserRef)))).To(BeTrue()) + _, err := newPermission.ValidateUpdate(rootCtx, &permission, newPermission) + Expect(err).To(MatchError(ContainSubstring("updates on user, userReference, vhost and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on vhost", func() { newPermission := permission.DeepCopy() newPermission.Spec.Vhost = "new-vhost" - Expect(apierrors.IsForbidden(ignoreNilWarning(newPermission.ValidateUpdate(&permission)))).To(BeTrue()) + _, err := newPermission.ValidateUpdate(rootCtx, &permission, newPermission) + Expect(err).To(MatchError(ContainSubstring("updates on user, userReference, vhost and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on RabbitmqClusterReference", func() { @@ -82,7 +92,8 @@ var _ = Describe("permission webhook", func() { newPermission.Spec.RabbitmqClusterReference = RabbitmqClusterReference{ Name: "new-cluster", } - Expect(apierrors.IsForbidden(ignoreNilWarning(newPermission.ValidateUpdate(&permission)))).To(BeTrue()) + _, err := newPermission.ValidateUpdate(rootCtx, &permission, newPermission) + Expect(err).To(MatchError(ContainSubstring("updates on user, userReference, vhost and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on rabbitmqClusterReference.connectionSecret", func() { @@ -105,40 +116,46 @@ var _ = Describe("permission webhook", func() { }, }, } - new := connectionScr.DeepCopy() - new.Spec.RabbitmqClusterReference.ConnectionSecret.Name = "new-secret" - Expect(apierrors.IsForbidden(ignoreNilWarning(new.ValidateUpdate(&connectionScr)))).To(BeTrue()) + newPermission := connectionScr.DeepCopy() + newPermission.Spec.RabbitmqClusterReference.ConnectionSecret.Name = "new-secret" + _, err := newPermission.ValidateUpdate(rootCtx, &connectionScr, newPermission) + Expect(err).To(MatchError(ContainSubstring("updates on user, userReference, vhost and rabbitmqClusterReference are all forbidden"))) }) It("allows updates on permission.spec.permissions.configure", func() { newPermission := permission.DeepCopy() newPermission.Spec.Permissions.Configure = "?" - Expect(ignoreNilWarning(newPermission.ValidateUpdate(&permission))).To(Succeed()) + _, err := newPermission.ValidateUpdate(rootCtx, &permission, newPermission) + Expect(err).ToNot(HaveOccurred()) }) It("allows updates on permission.spec.permissions.read", func() { newPermission := permission.DeepCopy() newPermission.Spec.Permissions.Read = "?" - Expect(ignoreNilWarning(newPermission.ValidateUpdate(&permission))).To(Succeed()) + _, err := newPermission.ValidateUpdate(rootCtx, &permission, newPermission) + Expect(err).ToNot(HaveOccurred()) }) It("allows updates on permission.spec.permissions.write", func() { newPermission := permission.DeepCopy() newPermission.Spec.Permissions.Write = "?" - Expect(ignoreNilWarning(newPermission.ValidateUpdate(&permission))).To(Succeed()) + _, err := newPermission.ValidateUpdate(rootCtx, &permission, newPermission) + Expect(err).ToNot(HaveOccurred()) }) It("does not allow user and userReference to be specified at the same time", func() { newPermission := permission.DeepCopy() newPermission.Spec.UserReference = &corev1.LocalObjectReference{Name: "invalid"} - Expect(apierrors.IsInvalid(ignoreNilWarning(newPermission.ValidateUpdate(&permission)))).To(BeTrue()) + _, err := newPermission.ValidateUpdate(rootCtx, &permission, newPermission) + Expect(err).To(MatchError(ContainSubstring("cannot specify spec.user and spec.userReference at the same time"))) }) It("does not allow both user and userReference to be unset", func() { newPermission := permission.DeepCopy() newPermission.Spec.User = "" newPermission.Spec.UserReference = nil - Expect(apierrors.IsInvalid(ignoreNilWarning(newPermission.ValidateUpdate(&permission)))).To(BeTrue()) + _, err := newPermission.ValidateUpdate(rootCtx, &permission, newPermission) + Expect(err).To(MatchError(ContainSubstring("must specify either spec.user or spec.userReference"))) }) }) }) diff --git a/api/v1beta1/policy_webhook.go b/api/v1beta1/policy_webhook.go index deefc6cb..642e9bc1 100644 --- a/api/v1beta1/policy_webhook.go +++ b/api/v1beta1/policy_webhook.go @@ -1,6 +1,7 @@ package v1beta1 import ( + "context" "fmt" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -12,45 +13,55 @@ import ( func (p *Policy) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). + WithValidator(p). For(p). Complete() } // +kubebuilder:webhook:verbs=create;update,path=/validate-rabbitmq-com-v1beta1-policy,mutating=false,failurePolicy=fail,groups=rabbitmq.com,resources=policies,versions=v1beta1,name=vpolicy.kb.io,sideEffects=none,admissionReviewVersions=v1 -var _ webhook.Validator = &Policy{} +var _ webhook.CustomValidator = &Policy{} // ValidateCreate implements webhook.Validator so a webhook will be registered for the type // either rabbitmqClusterReference.name or rabbitmqClusterReference.connectionSecret must be provided but not both -func (p *Policy) ValidateCreate() (admission.Warnings, error) { - return p.Spec.RabbitmqClusterReference.ValidateOnCreate(p.GroupResource(), p.Name) +func (p *Policy) ValidateCreate(_ context.Context, obj runtime.Object) (warnings admission.Warnings, err error) { + policy, ok := obj.(*Policy) + if !ok { + return nil, fmt.Errorf("expected a RabbitMQ policy but got a %T", obj) + } + return nil, p.Spec.RabbitmqClusterReference.validate(policy.RabbitReference()) } // ValidateUpdate returns error type 'forbidden' for updates on policy name, vhost and rabbitmqClusterReference -func (p *Policy) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - oldPolicy, ok := old.(*Policy) +func (p *Policy) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (warnings admission.Warnings, err error) { + oldPolicy, ok := oldObj.(*Policy) + if !ok { + return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a policy but got a %T", oldObj)) + } + + newPolicy, ok := newObj.(*Policy) if !ok { - return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a policy but got a %T", old)) + return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a policy but got a %T", newObj)) } - detailMsg := "updates on name, vhost and rabbitmqClusterReference are all forbidden" - if p.Spec.Name != oldPolicy.Spec.Name { - return nil, apierrors.NewForbidden(p.GroupResource(), p.Name, + const detailMsg = "updates on name, vhost and rabbitmqClusterReference are all forbidden" + if newPolicy.Spec.Name != oldPolicy.Spec.Name { + return nil, apierrors.NewForbidden(newPolicy.GroupResource(), newPolicy.Name, field.Forbidden(field.NewPath("spec", "name"), detailMsg)) } - if p.Spec.Vhost != oldPolicy.Spec.Vhost { - return nil, apierrors.NewForbidden(p.GroupResource(), p.Name, + if newPolicy.Spec.Vhost != oldPolicy.Spec.Vhost { + return nil, apierrors.NewForbidden(newPolicy.GroupResource(), newPolicy.Name, field.Forbidden(field.NewPath("spec", "vhost"), detailMsg)) } - if !oldPolicy.Spec.RabbitmqClusterReference.Matches(&p.Spec.RabbitmqClusterReference) { - return nil, apierrors.NewForbidden(p.GroupResource(), p.Name, + if !oldPolicy.Spec.RabbitmqClusterReference.Matches(&newPolicy.Spec.RabbitmqClusterReference) { + return nil, apierrors.NewForbidden(newPolicy.GroupResource(), newPolicy.Name, field.Forbidden(field.NewPath("spec", "rabbitmqClusterReference"), detailMsg)) } return nil, nil } -func (p *Policy) ValidateDelete() (admission.Warnings, error) { +func (p *Policy) ValidateDelete(_ context.Context, _ runtime.Object) (warnings admission.Warnings, err error) { return nil, nil } diff --git a/api/v1beta1/policy_webhook_test.go b/api/v1beta1/policy_webhook_test.go index bcb895b8..c593bab6 100644 --- a/api/v1beta1/policy_webhook_test.go +++ b/api/v1beta1/policy_webhook_test.go @@ -1,43 +1,48 @@ package v1beta1 import ( + "context" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) var _ = Describe("policy webhook", func() { - var policy = Policy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - }, - Spec: PolicySpec{ - Name: "test", - Vhost: "/test", - Pattern: "a-pattern", - ApplyTo: "all", - Priority: 0, - RabbitmqClusterReference: RabbitmqClusterReference{ - Name: "a-cluster", + var ( + policy = Policy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", }, - }, - } + Spec: PolicySpec{ + Name: "test", + Vhost: "/test", + Pattern: "a-pattern", + ApplyTo: "all", + Priority: 0, + RabbitmqClusterReference: RabbitmqClusterReference{ + Name: "a-cluster", + }, + }, + } + rootCtx = context.Background() + ) Context("ValidateCreate", func() { It("does not allow both spec.rabbitmqClusterReference.name and spec.rabbitmqClusterReference.connectionSecret be configured", func() { notAllowed := policy.DeepCopy() notAllowed.Spec.RabbitmqClusterReference.ConnectionSecret = &corev1.LocalObjectReference{Name: "some-secret"} - Expect(apierrors.IsForbidden(ignoreNilWarning(notAllowed.ValidateCreate()))).To(BeTrue()) + _, err := notAllowed.ValidateCreate(rootCtx, notAllowed) + Expect(err).To(MatchError(ContainSubstring("invalid RabbitmqClusterReference: do not provide both name and connectionSecret"))) }) It("spec.rabbitmqClusterReference.name and spec.rabbitmqClusterReference.connectionSecret cannot both be empty", func() { notAllowed := policy.DeepCopy() notAllowed.Spec.RabbitmqClusterReference.Name = "" notAllowed.Spec.RabbitmqClusterReference.ConnectionSecret = nil - Expect(apierrors.IsForbidden(ignoreNilWarning(notAllowed.ValidateCreate()))).To(BeTrue()) + _, err := notAllowed.ValidateCreate(rootCtx, notAllowed) + Expect(err).To(MatchError(ContainSubstring("invalid RabbitmqClusterReference: must provide either name or connectionSecret"))) }) }) @@ -45,13 +50,15 @@ var _ = Describe("policy webhook", func() { It("does not allow updates on policy name", func() { newPolicy := policy.DeepCopy() newPolicy.Spec.Name = "new-name" - Expect(apierrors.IsForbidden(ignoreNilWarning(newPolicy.ValidateUpdate(&policy)))).To(BeTrue()) + _, err := newPolicy.ValidateUpdate(rootCtx, &policy, newPolicy) + Expect(err).To(MatchError(ContainSubstring("updates on name, vhost and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on vhost", func() { newPolicy := policy.DeepCopy() newPolicy.Spec.Vhost = "new-vhost" - Expect(apierrors.IsForbidden(ignoreNilWarning(newPolicy.ValidateUpdate(&policy)))).To(BeTrue()) + _, err := newPolicy.ValidateUpdate(rootCtx, &policy, newPolicy) + Expect(err).To(MatchError(ContainSubstring("updates on name, vhost and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on RabbitmqClusterReference", func() { @@ -59,7 +66,8 @@ var _ = Describe("policy webhook", func() { newPolicy.Spec.RabbitmqClusterReference = RabbitmqClusterReference{ Name: "new-cluster", } - Expect(apierrors.IsForbidden(ignoreNilWarning(newPolicy.ValidateUpdate(&policy)))).To(BeTrue()) + _, err := newPolicy.ValidateUpdate(rootCtx, &policy, newPolicy) + Expect(err).To(MatchError(ContainSubstring("updates on name, vhost and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on rabbitmqClusterReference.connectionSecret", func() { @@ -80,33 +88,38 @@ var _ = Describe("policy webhook", func() { }, }, } - new := connectionScr.DeepCopy() - new.Spec.RabbitmqClusterReference.ConnectionSecret.Name = "new-secret" - Expect(apierrors.IsForbidden(ignoreNilWarning(new.ValidateUpdate(&connectionScr)))).To(BeTrue()) + newPolicy := connectionScr.DeepCopy() + newPolicy.Spec.RabbitmqClusterReference.ConnectionSecret.Name = "new-secret" + _, err := newPolicy.ValidateUpdate(rootCtx, &connectionScr, newPolicy) + Expect(err).To(MatchError(ContainSubstring("updates on name, vhost and rabbitmqClusterReference are all forbidden"))) }) It("allows updates on policy.spec.pattern", func() { newPolicy := policy.DeepCopy() newPolicy.Spec.Pattern = "new-pattern" - Expect(ignoreNilWarning(newPolicy.ValidateUpdate(&policy))).To(Succeed()) + _, err := newPolicy.ValidateUpdate(rootCtx, &policy, newPolicy) + Expect(err).ToNot(HaveOccurred()) }) It("allows updates on policy.spec.applyTo", func() { newPolicy := policy.DeepCopy() newPolicy.Spec.ApplyTo = "queues" - Expect(ignoreNilWarning(newPolicy.ValidateUpdate(&policy))).To(Succeed()) + _, err := newPolicy.ValidateUpdate(rootCtx, &policy, newPolicy) + Expect(err).ToNot(HaveOccurred()) }) It("allows updates on policy.spec.priority", func() { newPolicy := policy.DeepCopy() newPolicy.Spec.Priority = 1000 - Expect(ignoreNilWarning(newPolicy.ValidateUpdate(&policy))).To(Succeed()) + _, err := newPolicy.ValidateUpdate(rootCtx, &policy, newPolicy) + Expect(err).ToNot(HaveOccurred()) }) It("allows updates on policy.spec.definition", func() { newPolicy := policy.DeepCopy() newPolicy.Spec.Definition = &runtime.RawExtension{Raw: []byte(`{"key":"new-definition-value"}`)} - Expect(ignoreNilWarning(newPolicy.ValidateUpdate(&policy))).To(Succeed()) + _, err := newPolicy.ValidateUpdate(rootCtx, &policy, newPolicy) + Expect(err).ToNot(HaveOccurred()) }) }) }) diff --git a/api/v1beta1/queue_webhook.go b/api/v1beta1/queue_webhook.go index f9c94d97..23ac25e5 100644 --- a/api/v1beta1/queue_webhook.go +++ b/api/v1beta1/queue_webhook.go @@ -1,6 +1,7 @@ package v1beta1 import ( + "context" "fmt" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -12,72 +13,82 @@ import ( func (q *Queue) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). + WithValidator(q). For(q). Complete() } // +kubebuilder:webhook:verbs=create;update,path=/validate-rabbitmq-com-v1beta1-queue,mutating=false,failurePolicy=fail,groups=rabbitmq.com,resources=queues,versions=v1beta1,name=vqueue.kb.io,sideEffects=none,admissionReviewVersions=v1sideEffects=none,admissionReviewVersions=v1 -var _ webhook.Validator = &Queue{} +var _ webhook.CustomValidator = &Queue{} // ValidateCreate implements webhook.Validator so a webhook will be registered for the type // either rabbitmqClusterReference.name or rabbitmqClusterReference.connectionSecret must be provided but not both -func (q *Queue) ValidateCreate() (admission.Warnings, error) { - if q.Spec.Type == "quorum" && q.Spec.Durable == false { - return nil, apierrors.NewForbidden(q.GroupResource(), q.Name, +func (q *Queue) ValidateCreate(_ context.Context, obj runtime.Object) (warnings admission.Warnings, err error) { + inQueue, ok := obj.(*Queue) + if !ok { + return nil, fmt.Errorf("expected RabbitMQ queue, got %T", obj) + } + if inQueue.Spec.Type == "quorum" && inQueue.Spec.Durable == false { + return nil, apierrors.NewForbidden(inQueue.GroupResource(), inQueue.Name, field.Forbidden(field.NewPath("spec", "durable"), "Quorum queues must have durable set to true")) } - return q.Spec.RabbitmqClusterReference.ValidateOnCreate(q.GroupResource(), q.Name) + return nil, q.Spec.RabbitmqClusterReference.validate(inQueue.RabbitReference()) } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type // returns error type 'forbidden' for updates that the controller chooses to disallow: queue name/vhost/rabbitmqClusterReference // returns error type 'invalid' for updates that will be rejected by rabbitmq server: queue types/autoDelete/durable // queue arguments not handled because implementation couldn't change -func (q *Queue) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - oldQueue, ok := old.(*Queue) +func (q *Queue) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (warnings admission.Warnings, err error) { + oldQueue, ok := oldObj.(*Queue) + if !ok { + return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a queue but got a %T", oldObj)) + } + + newQueue, ok := newObj.(*Queue) if !ok { - return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a queue but got a %T", old)) + return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a queue but got a %T", newObj)) } var allErrs field.ErrorList - detailMsg := "updates on name, vhost, and rabbitmqClusterReference are all forbidden" - if q.Spec.Name != oldQueue.Spec.Name { - return nil, apierrors.NewForbidden(q.GroupResource(), q.Name, + const detailMsg = "updates on name, vhost, and rabbitmqClusterReference are all forbidden" + if newQueue.Spec.Name != oldQueue.Spec.Name { + return nil, apierrors.NewForbidden(newQueue.GroupResource(), newQueue.Name, field.Forbidden(field.NewPath("spec", "name"), detailMsg)) } - if q.Spec.Vhost != oldQueue.Spec.Vhost { - return nil, apierrors.NewForbidden(q.GroupResource(), q.Name, + if newQueue.Spec.Vhost != oldQueue.Spec.Vhost { + return nil, apierrors.NewForbidden(newQueue.GroupResource(), newQueue.Name, field.Forbidden(field.NewPath("spec", "vhost"), detailMsg)) } - if !oldQueue.Spec.RabbitmqClusterReference.Matches(&q.Spec.RabbitmqClusterReference) { - return nil, apierrors.NewForbidden(q.GroupResource(), q.Name, + if !oldQueue.Spec.RabbitmqClusterReference.Matches(&newQueue.Spec.RabbitmqClusterReference) { + return nil, apierrors.NewForbidden(newQueue.GroupResource(), newQueue.Name, field.Forbidden(field.NewPath("spec", "rabbitmqClusterReference"), detailMsg)) } - if q.Spec.Type != oldQueue.Spec.Type { + if newQueue.Spec.Type != oldQueue.Spec.Type { allErrs = append(allErrs, field.Invalid( field.NewPath("spec", "type"), - q.Spec.Type, + newQueue.Spec.Type, "queue type cannot be updated", )) } - if q.Spec.AutoDelete != oldQueue.Spec.AutoDelete { + if newQueue.Spec.AutoDelete != oldQueue.Spec.AutoDelete { allErrs = append(allErrs, field.Invalid( field.NewPath("spec", "autoDelete"), - q.Spec.AutoDelete, + newQueue.Spec.AutoDelete, "autoDelete cannot be updated", )) } - if q.Spec.Durable != oldQueue.Spec.Durable { + if newQueue.Spec.Durable != oldQueue.Spec.Durable { allErrs = append(allErrs, field.Invalid( field.NewPath("spec", "durable"), - q.Spec.AutoDelete, + newQueue.Spec.AutoDelete, "durable cannot be updated", )) } @@ -86,9 +97,9 @@ func (q *Queue) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { return nil, nil } - return nil, apierrors.NewInvalid(GroupVersion.WithKind("Queue").GroupKind(), q.Name, allErrs) + return nil, allErrs.ToAggregate() } -func (q *Queue) ValidateDelete() (admission.Warnings, error) { +func (q *Queue) ValidateDelete(_ context.Context, _ runtime.Object) (warnings admission.Warnings, err error) { return nil, nil } diff --git a/api/v1beta1/queue_webhook_test.go b/api/v1beta1/queue_webhook_test.go index 09ba709b..6bbbbe1f 100644 --- a/api/v1beta1/queue_webhook_test.go +++ b/api/v1beta1/queue_webhook_test.go @@ -1,49 +1,57 @@ package v1beta1 import ( + "context" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) var _ = Describe("queue webhook", func() { - var queue = Queue{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-queue", - }, - Spec: QueueSpec{ - Name: "test", - Vhost: "/a-vhost", - Type: "quorum", - Durable: false, - AutoDelete: true, - RabbitmqClusterReference: RabbitmqClusterReference{ - Name: "some-cluster", + var ( + queue = Queue{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-queue", }, - }, - } + Spec: QueueSpec{ + Name: "test", + Vhost: "/a-vhost", + Type: "quorum", + Durable: false, + AutoDelete: true, + RabbitmqClusterReference: RabbitmqClusterReference{ + Name: "some-cluster", + }, + }, + } + rootCtx = context.Background() + ) Context("ValidateCreate", func() { It("does not allow both spec.rabbitmqClusterReference.name and spec.rabbitmqClusterReference.connectionSecret be configured", func() { notAllowedQ := queue.DeepCopy() + notAllowedQ.Spec.Durable = true notAllowedQ.Spec.RabbitmqClusterReference.ConnectionSecret = &corev1.LocalObjectReference{Name: "some-secret"} - Expect(apierrors.IsForbidden(ignoreNilWarning(notAllowedQ.ValidateCreate()))).To(BeTrue()) + _, err := notAllowedQ.ValidateCreate(rootCtx, notAllowedQ) + Expect(err).To(MatchError(ContainSubstring("invalid RabbitmqClusterReference: do not provide both name and connectionSecret"))) }) It("spec.rabbitmqClusterReference.name and spec.rabbitmqClusterReference.connectionSecret cannot both be empty", func() { notAllowedQ := queue.DeepCopy() + notAllowedQ.Spec.Durable = true notAllowedQ.Spec.RabbitmqClusterReference.Name = "" notAllowedQ.Spec.RabbitmqClusterReference.ConnectionSecret = nil - Expect(apierrors.IsForbidden(ignoreNilWarning(notAllowedQ.ValidateCreate()))).To(BeTrue()) + _, err := notAllowedQ.ValidateCreate(rootCtx, notAllowedQ) + Expect(err).To(MatchError(ContainSubstring("invalid RabbitmqClusterReference: must provide either name or connectionSecret"))) }) It("does not allow non-durable quorum queues", func() { notAllowedQ := queue.DeepCopy() notAllowedQ.Spec.AutoDelete = false - Expect(apierrors.IsForbidden(ignoreNilWarning(notAllowedQ.ValidateCreate()))).To(BeTrue(), "Expected 'forbidden' response for non-durable quorum queue") + _, err := notAllowedQ.ValidateCreate(rootCtx, notAllowedQ) + Expect(err).To(MatchError(ContainSubstring("Quorum queues must have durable set to true"))) }) }) @@ -51,13 +59,15 @@ var _ = Describe("queue webhook", func() { It("does not allow updates on queue name", func() { newQueue := queue.DeepCopy() newQueue.Spec.Name = "new-name" - Expect(apierrors.IsForbidden(ignoreNilWarning(newQueue.ValidateUpdate(&queue)))).To(BeTrue()) + _, err := newQueue.ValidateUpdate(rootCtx, &queue, newQueue) + Expect(err).To(MatchError(ContainSubstring("updates on name, vhost, and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on vhost", func() { newQueue := queue.DeepCopy() newQueue.Spec.Vhost = "/new-vhost" - Expect(apierrors.IsForbidden(ignoreNilWarning(newQueue.ValidateUpdate(&queue)))).To(BeTrue()) + _, err := newQueue.ValidateUpdate(rootCtx, &queue, newQueue) + Expect(err).To(MatchError(ContainSubstring("updates on name, vhost, and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on rabbitmqClusterReference.name", func() { @@ -65,7 +75,8 @@ var _ = Describe("queue webhook", func() { newQueue.Spec.RabbitmqClusterReference = RabbitmqClusterReference{ Name: "new-cluster", } - Expect(apierrors.IsForbidden(ignoreNilWarning(newQueue.ValidateUpdate(&queue)))).To(BeTrue()) + _, err := newQueue.ValidateUpdate(rootCtx, &queue, newQueue) + Expect(err).To(MatchError(ContainSubstring("updates on name, vhost, and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on rabbitmqClusterReference.namespace", func() { @@ -73,7 +84,8 @@ var _ = Describe("queue webhook", func() { newQueue.Spec.RabbitmqClusterReference = RabbitmqClusterReference{ Namespace: "new-ns", } - Expect(apierrors.IsForbidden(ignoreNilWarning(newQueue.ValidateUpdate(&queue)))).To(BeTrue()) + _, err := newQueue.ValidateUpdate(rootCtx, &queue, newQueue) + Expect(err).To(MatchError(ContainSubstring("updates on name, vhost, and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on rabbitmqClusterReference.connectionSecret", func() { @@ -92,25 +104,29 @@ var _ = Describe("queue webhook", func() { } newQueue := connectionScrQ.DeepCopy() newQueue.Spec.RabbitmqClusterReference.ConnectionSecret.Name = "new-secret" - Expect(apierrors.IsForbidden(ignoreNilWarning(newQueue.ValidateUpdate(&connectionScrQ)))).To(BeTrue()) + _, err := newQueue.ValidateUpdate(rootCtx, &connectionScrQ, newQueue) + Expect(err).To(MatchError(ContainSubstring("updates on name, vhost, and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on queue type", func() { newQueue := queue.DeepCopy() newQueue.Spec.Type = "classic" - Expect(apierrors.IsInvalid(ignoreNilWarning(newQueue.ValidateUpdate(&queue)))).To(BeTrue()) + _, err := newQueue.ValidateUpdate(rootCtx, &queue, newQueue) + Expect(err).To(MatchError(ContainSubstring("queue type cannot be updated"))) }) It("does not allow updates on durable", func() { newQueue := queue.DeepCopy() newQueue.Spec.Durable = true - Expect(apierrors.IsInvalid(ignoreNilWarning(newQueue.ValidateUpdate(&queue)))).To(BeTrue()) + _, err := newQueue.ValidateUpdate(rootCtx, &queue, newQueue) + Expect(err).To(MatchError(ContainSubstring("durable cannot be updated"))) }) It("does not allow updates on autoDelete", func() { newQueue := queue.DeepCopy() newQueue.Spec.AutoDelete = false - Expect(apierrors.IsInvalid(ignoreNilWarning(newQueue.ValidateUpdate(&queue)))).To(BeTrue()) + _, err := newQueue.ValidateUpdate(rootCtx, &queue, newQueue) + Expect(err).To(MatchError(ContainSubstring("autoDelete cannot be updated"))) }) }) }) diff --git a/api/v1beta1/rabbitmq_cluster_reference.go b/api/v1beta1/rabbitmq_cluster_reference.go index 50c45f61..77444dd7 100644 --- a/api/v1beta1/rabbitmq_cluster_reference.go +++ b/api/v1beta1/rabbitmq_cluster_reference.go @@ -1,10 +1,9 @@ package v1beta1 import ( + "errors" corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/validation/field" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) @@ -49,17 +48,18 @@ func (r *RabbitmqClusterReference) Matches(new *RabbitmqClusterReference) bool { // ValidateOnCreate validates RabbitmqClusterReference on resources create // either rabbitmqClusterReference.name or rabbitmqClusterReference.connectionSecret must be provided but not both; else it errors -func (ref *RabbitmqClusterReference) ValidateOnCreate(groupResource schema.GroupResource, name string) (admission.Warnings, error) { +func (r *RabbitmqClusterReference) ValidateOnCreate(_ schema.GroupResource, _ string) (admission.Warnings, error) { + // TODO: make this function private when we deprecate or promote v1alpha1 SuperStreamController + return nil, r.validate(*r) +} + +func (r *RabbitmqClusterReference) validate(ref RabbitmqClusterReference) error { if ref.Name != "" && ref.ConnectionSecret != nil { - return nil, apierrors.NewForbidden(groupResource, name, - field.Forbidden(field.NewPath("spec", "rabbitmqClusterReference"), - "do not provide both spec.rabbitmqClusterReference.name and spec.rabbitmqClusterReference.connectionSecret")) + return errors.New("invalid RabbitmqClusterReference: do not provide both name and connectionSecret") } if ref.Name == "" && ref.ConnectionSecret == nil { - return nil, apierrors.NewForbidden(groupResource, name, - field.Forbidden(field.NewPath("spec", "rabbitmqClusterReference"), - "must provide either spec.rabbitmqClusterReference.name or spec.rabbitmqClusterReference.connectionSecret")) + return errors.New("invalid RabbitmqClusterReference: must provide either name or connectionSecret") } - return nil, nil + return nil } diff --git a/api/v1beta1/rabbitmq_cluster_reference_test.go b/api/v1beta1/rabbitmq_cluster_reference_test.go index 79c98a91..a7a0c89d 100644 --- a/api/v1beta1/rabbitmq_cluster_reference_test.go +++ b/api/v1beta1/rabbitmq_cluster_reference_test.go @@ -4,7 +4,6 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime/schema" ) @@ -24,51 +23,51 @@ var _ = Describe("RabbitmqClusterReference", func() { Context("Matches", func() { When("name is different", func() { It("returns false", func() { - new := reference.DeepCopy() - new.Name = "new-name" - Expect(reference.Matches(new)).To(BeFalse()) + newReference := reference.DeepCopy() + newReference.Name = "new-name" + Expect(reference.Matches(newReference)).To(BeFalse()) }) }) When("namespace is different", func() { It("returns false", func() { - new := reference.DeepCopy() - new.Namespace = "new-ns" - Expect(reference.Matches(new)).To(BeFalse()) + newReference := reference.DeepCopy() + newReference.Namespace = "new-ns" + Expect(reference.Matches(newReference)).To(BeFalse()) }) }) When("connectionSecret.name is different", func() { It("returns false", func() { - new := reference.DeepCopy() - new.ConnectionSecret.Name = "new-secret-name" - Expect(reference.Matches(new)).To(BeFalse()) + newReference := reference.DeepCopy() + newReference.ConnectionSecret.Name = "new-secret-name" + Expect(reference.Matches(newReference)).To(BeFalse()) }) }) When("connectionSecret is removed", func() { It("returns false", func() { - new := reference.DeepCopy() - new.ConnectionSecret = nil - Expect(reference.Matches(new)).To(BeFalse()) + newReference := reference.DeepCopy() + newReference.ConnectionSecret = nil + Expect(reference.Matches(newReference)).To(BeFalse()) }) }) When("connectionSecret is added", func() { It("returns false", func() { reference.ConnectionSecret = nil - new := reference.DeepCopy() - new.ConnectionSecret = &v1.LocalObjectReference{ + newReference := reference.DeepCopy() + newReference.ConnectionSecret = &v1.LocalObjectReference{ Name: "a-secret-name", } - Expect(reference.Matches(new)).To(BeFalse()) + Expect(reference.Matches(newReference)).To(BeFalse()) }) }) When("RabbitmqClusterReference stayed the same", func() { It("returns true", func() { - new := reference.DeepCopy() - Expect(reference.Matches(new)).To(BeTrue()) + newReference := reference.DeepCopy() + Expect(reference.Matches(newReference)).To(BeTrue()) }) }) }) @@ -94,7 +93,8 @@ var _ = Describe("RabbitmqClusterReference", func() { It("returns a forbidden api error", func() { reference.Name = "a-cluster" reference.ConnectionSecret = &v1.LocalObjectReference{Name: "a-secret-name"} - Expect(apierrors.IsForbidden(ignoreNilWarning(reference.ValidateOnCreate(schema.GroupResource{}, "a-resource")))).To(BeTrue()) + _, err := reference.ValidateOnCreate(schema.GroupResource{}, "a-resource") + Expect(err).To(MatchError(ContainSubstring("invalid RabbitmqClusterReference: do not provide both name and connectionSecret"))) }) }) @@ -102,7 +102,8 @@ var _ = Describe("RabbitmqClusterReference", func() { It("returns a forbidden api error", func() { reference.ConnectionSecret = nil reference.Name = "" - Expect(apierrors.IsForbidden(ignoreNilWarning(reference.ValidateOnCreate(schema.GroupResource{}, "a-resource")))).To(BeTrue()) + _, err := reference.ValidateOnCreate(schema.GroupResource{}, "a-resource") + Expect(err).To(MatchError(ContainSubstring("invalid RabbitmqClusterReference: must provide either name or connectionSecret"))) }) }) }) diff --git a/api/v1beta1/schemareplication_webhook.go b/api/v1beta1/schemareplication_webhook.go index 609af3b3..ca30cb84 100644 --- a/api/v1beta1/schemareplication_webhook.go +++ b/api/v1beta1/schemareplication_webhook.go @@ -1,6 +1,7 @@ package v1beta1 import ( + "context" "fmt" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -12,55 +13,67 @@ import ( func (s *SchemaReplication) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). + WithValidator(s). For(s). Complete() } // +kubebuilder:webhook:verbs=create;update,path=/validate-rabbitmq-com-v1beta1-schemareplication,mutating=false,failurePolicy=fail,groups=rabbitmq.com,resources=schemareplications,versions=v1beta1,name=vschemareplication.kb.io,sideEffects=none,admissionReviewVersions=v1 -var _ webhook.Validator = &SchemaReplication{} +var _ webhook.CustomValidator = &SchemaReplication{} -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type. -// either secretBackend.vault.secretPath or upstreamSecret must be provided but not both. -// either rabbitmqClusterReference.name or rabbitmqClusterReference.connectionSecret must be provided but not both. -func (s *SchemaReplication) ValidateCreate() (admission.Warnings, error) { - if err := s.validateSecret(); err != nil { +// ValidateCreate - either secretBackend.vault.secretPath or upstreamSecret must +// be provided but not both. Either rabbitmqClusterReference.name or +// rabbitmqClusterReference.connectionSecret must be provided but not both. +func (s *SchemaReplication) ValidateCreate(_ context.Context, obj runtime.Object) (warnings admission.Warnings, err error) { + sch, ok := obj.(*SchemaReplication) + if !ok { + return nil, fmt.Errorf("expected a schema replication type but got a %T", obj) + } + err = sch.validateSecret() + if err != nil { return nil, err } - return s.Spec.RabbitmqClusterReference.ValidateOnCreate(s.GroupResource(), s.Name) + + return nil, s.Spec.RabbitmqClusterReference.validate(sch.RabbitReference()) } -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type. -// either secretBackend.vault.secretPath or upstreamSecret must be provided but not both. -func (s *SchemaReplication) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - oldReplication, ok := old.(*SchemaReplication) +// ValidateUpdate - either secretBackend.vault.secretPath or upstreamSecret must +// be provided but not both. +func (s *SchemaReplication) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (warnings admission.Warnings, err error) { + oldReplication, ok := oldObj.(*SchemaReplication) + if !ok { + return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a schema replication type but got a %T", oldObj)) + } + + newReplication, ok := newObj.(*SchemaReplication) if !ok { - return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a schema replication type but got a %T", old)) + return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a schema replication type but got a %T", newObj)) } - if !oldReplication.Spec.RabbitmqClusterReference.Matches(&s.Spec.RabbitmqClusterReference) { - return nil, apierrors.NewForbidden(s.GroupResource(), s.Name, + if !oldReplication.Spec.RabbitmqClusterReference.Matches(&newReplication.Spec.RabbitmqClusterReference) { + return nil, apierrors.NewForbidden(newReplication.GroupResource(), newReplication.Name, field.Forbidden(field.NewPath("spec", "rabbitmqClusterReference"), "update on rabbitmqClusterReference is forbidden")) } - return nil, s.validateSecret() + return nil, newReplication.validateSecret() } // ValidateDelete no validation on delete -func (s *SchemaReplication) ValidateDelete() (admission.Warnings, error) { +func (s *SchemaReplication) ValidateDelete(_ context.Context, _ runtime.Object) (warnings admission.Warnings, err error) { return nil, nil } func (s *SchemaReplication) validateSecret() error { - if s.Spec.UpstreamSecret != nil && s.Spec.UpstreamSecret.Name != "" && s.Spec.SecretBackend.Vault != nil && s.Spec.SecretBackend.Vault.SecretPath != "" { - return apierrors.NewForbidden(s.GroupResource(), s.Name, - field.Forbidden(field.NewPath("spec"), - "do not provide both secretBackend.vault.secretPath and upstreamSecret")) + if s.Spec.UpstreamSecret != nil && + s.Spec.UpstreamSecret.Name != "" && + s.Spec.SecretBackend.Vault != nil && + s.Spec.SecretBackend.Vault.SecretPath != "" { + return field.Forbidden(field.NewPath("spec"), "do not provide both secretBackend.vault.secretPath and upstreamSecret") } - if (s.Spec.UpstreamSecret == nil || s.Spec.UpstreamSecret.Name == "") && (s.Spec.SecretBackend.Vault == nil || s.Spec.SecretBackend.Vault.SecretPath == "") { - return apierrors.NewForbidden(s.GroupResource(), s.Name, - field.Forbidden(field.NewPath("spec"), - "must provide either secretBackend.vault.secretPath or upstreamSecret")) + if (s.Spec.UpstreamSecret == nil || s.Spec.UpstreamSecret.Name == "") && + (s.Spec.SecretBackend.Vault == nil || s.Spec.SecretBackend.Vault.SecretPath == "") { + return field.Forbidden(field.NewPath("spec"), "must provide either secretBackend.vault.secretPath or upstreamSecret") } return nil } diff --git a/api/v1beta1/schemareplication_webhook_test.go b/api/v1beta1/schemareplication_webhook_test.go index 192e9fde..0f5a5c86 100644 --- a/api/v1beta1/schemareplication_webhook_test.go +++ b/api/v1beta1/schemareplication_webhook_test.go @@ -1,41 +1,46 @@ package v1beta1 import ( + "context" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) var _ = Describe("schema-replication webhook", func() { - var replication = SchemaReplication{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-replication", - }, - Spec: SchemaReplicationSpec{ - UpstreamSecret: &corev1.LocalObjectReference{ - Name: "a-secret", + var ( + replication = SchemaReplication{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-replication", }, - Endpoints: "abc.rmq.com:1234", - RabbitmqClusterReference: RabbitmqClusterReference{ - Name: "a-cluster", + Spec: SchemaReplicationSpec{ + UpstreamSecret: &corev1.LocalObjectReference{ + Name: "a-secret", + }, + Endpoints: "abc.rmq.com:1234", + RabbitmqClusterReference: RabbitmqClusterReference{ + Name: "a-cluster", + }, }, - }, - } + } + rootCtx = context.Background() + ) Context("ValidateCreate", func() { It("does not allow both spec.rabbitmqClusterReference.name and spec.rabbitmqClusterReference.connectionSecret be configured", func() { notAllowed := replication.DeepCopy() notAllowed.Spec.RabbitmqClusterReference.ConnectionSecret = &corev1.LocalObjectReference{Name: "some-secret"} - Expect(apierrors.IsForbidden(ignoreNilWarning(notAllowed.ValidateCreate()))).To(BeTrue()) + _, err := notAllowed.ValidateCreate(rootCtx, notAllowed) + Expect(err).To(MatchError(ContainSubstring("invalid RabbitmqClusterReference: do not provide both name and connectionSecret"))) }) It("spec.rabbitmqClusterReference.name and spec.rabbitmqClusterReference.connectionSecret cannot both be empty", func() { notAllowed := replication.DeepCopy() notAllowed.Spec.RabbitmqClusterReference.Name = "" notAllowed.Spec.RabbitmqClusterReference.ConnectionSecret = nil - Expect(apierrors.IsForbidden(ignoreNilWarning(notAllowed.ValidateCreate()))).To(BeTrue()) + _, err := notAllowed.ValidateCreate(rootCtx, notAllowed) + Expect(err).To(MatchError(ContainSubstring("invalid RabbitmqClusterReference: must provide either name or connectionSecret"))) }) It("does not allow both spec.upstreamSecret and spec.secretBackend.vault.userPath be configured", func() { @@ -45,16 +50,18 @@ var _ = Describe("schema-replication webhook", func() { SecretPath: "not-good", }, } - Expect(apierrors.IsForbidden(ignoreNilWarning(notAllowed.ValidateCreate()))).To(BeTrue()) + _, err := notAllowed.ValidateCreate(rootCtx, notAllowed) + Expect(err).To(MatchError(ContainSubstring("do not provide both secretBackend.vault.secretPath and upstreamSecret"))) }) - It("spec.upstreamSecret and spec.secretBackend.vault.userPath cannot both be not configured", func() { + It("validates that either upstream secret or vault backend are configured", func() { notAllowed := replication.DeepCopy() notAllowed.Spec.SecretBackend = SecretBackend{ Vault: &VaultSpec{}, } notAllowed.Spec.UpstreamSecret.Name = "" - Expect(apierrors.IsForbidden(ignoreNilWarning(notAllowed.ValidateCreate()))).To(BeTrue()) + _, err := notAllowed.ValidateCreate(rootCtx, notAllowed) + Expect(err).To(MatchError(ContainSubstring("must provide either secretBackend.vault.secretPath or upstreamSecret"))) }) }) @@ -64,7 +71,8 @@ var _ = Describe("schema-replication webhook", func() { updated.Spec.RabbitmqClusterReference = RabbitmqClusterReference{ Name: "different-cluster", } - Expect(apierrors.IsForbidden(ignoreNilWarning(updated.ValidateUpdate(&replication)))).To(BeTrue()) + _, err := updated.ValidateUpdate(rootCtx, &replication, updated) + Expect(err).To(MatchError(ContainSubstring("update on rabbitmqClusterReference is forbidden"))) }) It("does not allow both spec.upstreamSecret and spec.secretBackend.vault.userPath be configured", func() { @@ -74,7 +82,8 @@ var _ = Describe("schema-replication webhook", func() { SecretPath: "not-good", }, } - Expect(apierrors.IsForbidden(ignoreNilWarning(updated.ValidateUpdate(&replication)))).To(BeTrue()) + _, err := updated.ValidateUpdate(rootCtx, &replication, updated) + Expect(err).To(MatchError(ContainSubstring("do not provide both secretBackend.vault.secretPath and upstreamSecret"))) }) It("spec.upstreamSecret and spec.secretBackend.vault.userPath cannot both be not configured", func() { @@ -83,7 +92,8 @@ var _ = Describe("schema-replication webhook", func() { Vault: &VaultSpec{}, } updated.Spec.UpstreamSecret.Name = "" - Expect(apierrors.IsForbidden(ignoreNilWarning(updated.ValidateUpdate(&replication)))).To(BeTrue()) + _, err := updated.ValidateUpdate(rootCtx, &replication, updated) + Expect(err).To(MatchError(ContainSubstring("must provide either secretBackend.vault.secretPath or upstreamSecret"))) }) It("allows update on spec.secretBackend.vault.userPath", func() { @@ -94,7 +104,8 @@ var _ = Describe("schema-replication webhook", func() { }, } updated.Spec.UpstreamSecret.Name = "" - Expect(ignoreNilWarning(updated.ValidateUpdate(&replication))).To(Succeed()) + _, err := updated.ValidateUpdate(rootCtx, &replication, updated) + Expect(err).ToNot(HaveOccurred()) }) It("does not allow updates on rabbitmqClusterReference.connectionSecret", func() { @@ -116,7 +127,8 @@ var _ = Describe("schema-replication webhook", func() { } newObj := connectionScr.DeepCopy() newObj.Spec.RabbitmqClusterReference.ConnectionSecret.Name = "newObj-secret" - Expect(apierrors.IsForbidden(ignoreNilWarning(newObj.ValidateUpdate(&connectionScr)))).To(BeTrue()) + _, err := newObj.ValidateUpdate(rootCtx, &connectionScr, newObj) + Expect(err).To(MatchError(ContainSubstring("update on rabbitmqClusterReference is forbidden"))) }) It("allows updates on spec.upstreamSecret", func() { @@ -124,13 +136,15 @@ var _ = Describe("schema-replication webhook", func() { updated.Spec.UpstreamSecret = &corev1.LocalObjectReference{ Name: "a-different-secret", } - Expect(ignoreNilWarning(updated.ValidateUpdate(&replication))).To(Succeed()) + _, err := updated.ValidateUpdate(rootCtx, &replication, updated) + Expect(err).ToNot(HaveOccurred()) }) It("allows updates on spec.endpoints", func() { updated := replication.DeepCopy() updated.Spec.Endpoints = "abc.new-rmq:1111" - Expect(ignoreNilWarning(updated.ValidateUpdate(&replication))).To(Succeed()) + _, err := updated.ValidateUpdate(rootCtx, &replication, updated) + Expect(err).ToNot(HaveOccurred()) }) }) }) diff --git a/api/v1beta1/shovel_webhook.go b/api/v1beta1/shovel_webhook.go index 247c8b8b..6ae39e35 100644 --- a/api/v1beta1/shovel_webhook.go +++ b/api/v1beta1/shovel_webhook.go @@ -1,6 +1,7 @@ package v1beta1 import ( + "context" "fmt" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -12,67 +13,75 @@ import ( func (s *Shovel) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). + WithValidator(s). For(s). Complete() } // +kubebuilder:webhook:verbs=create;update,path=/validate-rabbitmq-com-v1beta1-shovel,mutating=false,failurePolicy=fail,groups=rabbitmq.com,resources=shovels,versions=v1beta1,name=vshovel.kb.io,sideEffects=none,admissionReviewVersions=v1 -var _ webhook.Validator = &Shovel{} +var _ webhook.CustomValidator = &Shovel{} -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type -// either rabbitmqClusterReference.name or rabbitmqClusterReference.connectionSecret must be provided but not both -func (s *Shovel) ValidateCreate() (admission.Warnings, error) { - if err := s.amqp10Validate(); err != nil { +// ValidateCreate - either rabbitmqClusterReference.name or +// rabbitmqClusterReference.connectionSecret must be provided, but not both +func (s *Shovel) ValidateCreate(_ context.Context, obj runtime.Object) (warnings admission.Warnings, err error) { + shovel, ok := obj.(*Shovel) + if !ok { + return nil, fmt.Errorf("expected a RabbitMQ shovel but got a %T", obj) + } + if err := shovel.amqp10Validate(); err != nil { return nil, err } - return s.Spec.RabbitmqClusterReference.ValidateOnCreate(s.GroupResource(), s.Name) + + return nil, s.Spec.RabbitmqClusterReference.validate(shovel.RabbitReference()) } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (s *Shovel) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - oldShovel, ok := old.(*Shovel) +func (s *Shovel) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (warnings admission.Warnings, err error) { + oldShovel, ok := oldObj.(*Shovel) if !ok { return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a shovel but got a %T", oldShovel)) } - if err := s.amqp10Validate(); err != nil { + newShovel, ok := newObj.(*Shovel) + if !ok { + return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a shovel but got a %T", newObj)) + } + + if err := newShovel.amqp10Validate(); err != nil { return nil, err } - detailMsg := "updates on name, vhost and rabbitmqClusterReference are all forbidden" - if s.Spec.Name != oldShovel.Spec.Name { - return nil, apierrors.NewForbidden(s.GroupResource(), s.Name, + const detailMsg = "updates on name, vhost and rabbitmqClusterReference are all forbidden" + if newShovel.Spec.Name != oldShovel.Spec.Name { + return nil, apierrors.NewForbidden(newShovel.GroupResource(), newShovel.Name, field.Forbidden(field.NewPath("spec", "name"), detailMsg)) } - if s.Spec.Vhost != oldShovel.Spec.Vhost { - return nil, apierrors.NewForbidden(s.GroupResource(), s.Name, + if newShovel.Spec.Vhost != oldShovel.Spec.Vhost { + return nil, apierrors.NewForbidden(newShovel.GroupResource(), newShovel.Name, field.Forbidden(field.NewPath("spec", "vhost"), detailMsg)) } - if !oldShovel.Spec.RabbitmqClusterReference.Matches(&s.Spec.RabbitmqClusterReference) { - return nil, apierrors.NewForbidden(s.GroupResource(), s.Name, + if !oldShovel.Spec.RabbitmqClusterReference.Matches(&newShovel.Spec.RabbitmqClusterReference) { + return nil, apierrors.NewForbidden(newShovel.GroupResource(), newShovel.Name, field.Forbidden(field.NewPath("spec", "rabbitmqClusterReference"), detailMsg)) } return nil, nil } -func (s *Shovel) ValidateDelete() (admission.Warnings, error) { +func (s *Shovel) ValidateDelete(_ context.Context, obj runtime.Object) (warnings admission.Warnings, err error) { return nil, nil } func (s *Shovel) amqp10Validate() error { - var errorList field.ErrorList if s.Spec.SourceProtocol == "amqp10" && s.Spec.SourceAddress == "" { - errorList = append(errorList, field.Required(field.NewPath("spec", "srcAddress"), - "must specify spec.srcAddress when spec.srcProtocol is amqp10")) - return apierrors.NewInvalid(GroupVersion.WithKind("Shovel").GroupKind(), s.Name, errorList) + return field.Required(field.NewPath("spec", "srcAddress"), + "must specify spec.srcAddress when spec.srcProtocol is amqp10") } if s.Spec.DestinationProtocol == "amqp10" && s.Spec.DestinationAddress == "" { - errorList = append(errorList, field.Required(field.NewPath("spec", "destAddress"), - "must specify spec.destAddress when spec.destProtocol is amqp10")) - return apierrors.NewInvalid(GroupVersion.WithKind("Shovel").GroupKind(), s.Name, errorList) + return field.Required(field.NewPath("spec", "destAddress"), + "must specify spec.destAddress when spec.destProtocol is amqp10") } return nil } diff --git a/api/v1beta1/shovel_webhook_test.go b/api/v1beta1/shovel_webhook_test.go index 47d769b9..0c5f90fd 100644 --- a/api/v1beta1/shovel_webhook_test.go +++ b/api/v1beta1/shovel_webhook_test.go @@ -1,81 +1,88 @@ package v1beta1 import ( + "context" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) var _ = Describe("shovel webhook", func() { - var shovel = Shovel{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - }, - Spec: ShovelSpec{ - Name: "test-upstream", - Vhost: "/a-vhost", - UriSecret: &corev1.LocalObjectReference{ - Name: "a-secret", + var ( + shovel = Shovel{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", }, - AckMode: "no-ack", - AddForwardHeaders: true, - DeleteAfter: "never", - DestinationAddForwardHeaders: true, - DestinationAddTimestampHeader: true, - DestinationAddress: "myQueue", - DestinationApplicationProperties: &runtime.RawExtension{Raw: []byte(`{"key": "a-property"}`)}, - DestinationExchange: "an-exchange", - DestinationExchangeKey: "a-key", - DestinationProperties: &runtime.RawExtension{Raw: []byte(`{"key": "a-property"}`)}, - DestinationProtocol: "amqp091", - DestinationPublishProperties: &runtime.RawExtension{Raw: []byte(`{"delivery_mode": 1}`)}, - DestinationMessageAnnotations: &runtime.RawExtension{Raw: []byte(`{"a-key": "an-annotation"}`)}, - DestinationQueue: "a-queue", - PrefetchCount: 10, - ReconnectDelay: 10, - SourceAddress: "myQueue", - SourceDeleteAfter: "never", - SourceExchange: "an-exchange", - SourceExchangeKey: "a-key", - SourcePrefetchCount: 10, - SourceProtocol: "amqp091", - SourceQueue: "a-queue", - SourceConsumerArgs: &runtime.RawExtension{Raw: []byte(`{"x-priority": 1}`)}, - RabbitmqClusterReference: RabbitmqClusterReference{ - Name: "a-cluster", + Spec: ShovelSpec{ + Name: "test-upstream", + Vhost: "/a-vhost", + UriSecret: &corev1.LocalObjectReference{ + Name: "a-secret", + }, + AckMode: "no-ack", + AddForwardHeaders: true, + DeleteAfter: "never", + DestinationAddForwardHeaders: true, + DestinationAddTimestampHeader: true, + DestinationAddress: "myQueue", + DestinationApplicationProperties: &runtime.RawExtension{Raw: []byte(`{"key": "a-property"}`)}, + DestinationExchange: "an-exchange", + DestinationExchangeKey: "a-key", + DestinationProperties: &runtime.RawExtension{Raw: []byte(`{"key": "a-property"}`)}, + DestinationProtocol: "amqp091", + DestinationPublishProperties: &runtime.RawExtension{Raw: []byte(`{"delivery_mode": 1}`)}, + DestinationMessageAnnotations: &runtime.RawExtension{Raw: []byte(`{"a-key": "an-annotation"}`)}, + DestinationQueue: "a-queue", + PrefetchCount: 10, + ReconnectDelay: 10, + SourceAddress: "myQueue", + SourceDeleteAfter: "never", + SourceExchange: "an-exchange", + SourceExchangeKey: "a-key", + SourcePrefetchCount: 10, + SourceProtocol: "amqp091", + SourceQueue: "a-queue", + SourceConsumerArgs: &runtime.RawExtension{Raw: []byte(`{"x-priority": 1}`)}, + RabbitmqClusterReference: RabbitmqClusterReference{ + Name: "a-cluster", + }, }, - }, - } + } + rootCtx = context.Background() + ) Context("ValidateCreate", func() { It("does not allow both spec.rabbitmqClusterReference.name and spec.rabbitmqClusterReference.connectionSecret be configured", func() { notAllowed := shovel.DeepCopy() notAllowed.Spec.RabbitmqClusterReference.ConnectionSecret = &corev1.LocalObjectReference{Name: "some-secret"} - Expect(apierrors.IsForbidden(ignoreNilWarning(notAllowed.ValidateCreate()))).To(BeTrue()) + _, err := notAllowed.ValidateCreate(rootCtx, notAllowed) + Expect(err).To(MatchError(ContainSubstring("invalid RabbitmqClusterReference: do not provide both name and connectionSecret"))) }) It("spec.rabbitmqClusterReference.name and spec.rabbitmqClusterReference.connectionSecret cannot both be empty", func() { notAllowed := shovel.DeepCopy() notAllowed.Spec.RabbitmqClusterReference.Name = "" notAllowed.Spec.RabbitmqClusterReference.ConnectionSecret = nil - Expect(apierrors.IsForbidden(ignoreNilWarning(notAllowed.ValidateCreate()))).To(BeTrue()) + _, err := notAllowed.ValidateCreate(rootCtx, notAllowed) + Expect(err).To(MatchError(ContainSubstring("invalid RabbitmqClusterReference: must provide either name or connectionSecret"))) }) It("spec.srcAddress must be set if spec.srcProtocol is amqp10", func() { notValid := shovel.DeepCopy() notValid.Spec.SourceProtocol = "amqp10" notValid.Spec.SourceAddress = "" - Expect(apierrors.IsInvalid(ignoreNilWarning(notValid.ValidateCreate()))).To(BeTrue()) + _, err := notValid.ValidateCreate(rootCtx, notValid) + Expect(err).To(MatchError(ContainSubstring("must specify spec.srcAddress when spec.srcProtocol is amqp10"))) }) It("spec.destAddress must be set if spec.destProtocol is amqp10", func() { notValid := shovel.DeepCopy() notValid.Spec.DestinationProtocol = "amqp10" notValid.Spec.DestinationAddress = "" - Expect(apierrors.IsInvalid(ignoreNilWarning(notValid.ValidateCreate()))).To(BeTrue()) + _, err := notValid.ValidateCreate(rootCtx, notValid) + Expect(err).To(MatchError(ContainSubstring("must specify spec.destAddress when spec.destProtocol is amqp10"))) }) }) @@ -83,13 +90,15 @@ var _ = Describe("shovel webhook", func() { It("does not allow updates on name", func() { newShovel := shovel.DeepCopy() newShovel.Spec.Name = "another-shovel" - Expect(apierrors.IsForbidden(ignoreNilWarning(newShovel.ValidateUpdate(&shovel)))).To(BeTrue()) + _, err := newShovel.ValidateUpdate(rootCtx, &shovel, newShovel) + Expect(err).To(MatchError(ContainSubstring("updates on name, vhost and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on vhost", func() { newShovel := shovel.DeepCopy() newShovel.Spec.Vhost = "another-vhost" - Expect(apierrors.IsForbidden(ignoreNilWarning(newShovel.ValidateUpdate(&shovel)))).To(BeTrue()) + _, err := newShovel.ValidateUpdate(rootCtx, &shovel, newShovel) + Expect(err).To(MatchError(ContainSubstring("updates on name, vhost and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on RabbitmqClusterReference", func() { @@ -97,21 +106,24 @@ var _ = Describe("shovel webhook", func() { newShovel.Spec.RabbitmqClusterReference = RabbitmqClusterReference{ Name: "another-cluster", } - Expect(apierrors.IsForbidden(ignoreNilWarning(newShovel.ValidateUpdate(&shovel)))).To(BeTrue()) + _, err := newShovel.ValidateUpdate(rootCtx, &shovel, newShovel) + Expect(err).To(MatchError(ContainSubstring("updates on name, vhost and rabbitmqClusterReference are all forbidden"))) }) It("spec.srcAddress must be set if spec.srcProtocol is amqp10", func() { newShovel := shovel.DeepCopy() newShovel.Spec.SourceProtocol = "amqp10" newShovel.Spec.SourceAddress = "" - Expect(apierrors.IsInvalid(ignoreNilWarning(newShovel.ValidateUpdate(&shovel)))).To(BeTrue()) + _, err := newShovel.ValidateCreate(rootCtx, newShovel) + Expect(err).To(MatchError(ContainSubstring("must specify spec.srcAddress when spec.srcProtocol is amqp10"))) }) It("spec.destAddress must be set if spec.destProtocol is amqp10", func() { newShovel := shovel.DeepCopy() newShovel.Spec.DestinationProtocol = "amqp10" newShovel.Spec.DestinationAddress = "" - Expect(apierrors.IsInvalid(ignoreNilWarning(newShovel.ValidateUpdate(&shovel)))).To(BeTrue()) + _, err := newShovel.ValidateCreate(rootCtx, newShovel) + Expect(err).To(MatchError(ContainSubstring("must specify spec.destAddress when spec.destProtocol is amqp10"))) }) It("does not allow updates on rabbitmqClusterReference.connectionSecret", func() { @@ -132,9 +144,10 @@ var _ = Describe("shovel webhook", func() { }, }, } - new := connectionScr.DeepCopy() - new.Spec.RabbitmqClusterReference.ConnectionSecret.Name = "new-secret" - Expect(apierrors.IsForbidden(ignoreNilWarning(new.ValidateUpdate(&connectionScr)))).To(BeTrue()) + newShovel := connectionScr.DeepCopy() + newShovel.Spec.RabbitmqClusterReference.ConnectionSecret.Name = "new-secret" + _, err := newShovel.ValidateUpdate(rootCtx, &connectionScr, newShovel) + Expect(err).To(MatchError(ContainSubstring("updates on name, vhost and rabbitmqClusterReference are all forbidden"))) }) It("allows updates on shovel configurations", func() { @@ -163,7 +176,8 @@ var _ = Describe("shovel webhook", func() { newShovel.Spec.SourceProtocol = "another-protocol" newShovel.Spec.SourceQueue = "another-queue" newShovel.Spec.SourceConsumerArgs = &runtime.RawExtension{Raw: []byte(`{"x-priority": 10}`)} - Expect(ignoreNilWarning(newShovel.ValidateUpdate(&shovel))).To(Succeed()) + _, err := newShovel.ValidateUpdate(rootCtx, &shovel, newShovel) + Expect(err).ToNot(HaveOccurred()) }) }) }) diff --git a/api/v1beta1/topicpermission_webhook.go b/api/v1beta1/topicpermission_webhook.go index 8f8066ad..dab427ee 100644 --- a/api/v1beta1/topicpermission_webhook.go +++ b/api/v1beta1/topicpermission_webhook.go @@ -1,6 +1,7 @@ package v1beta1 import ( + "context" "fmt" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" @@ -11,82 +12,90 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook" ) -func (r *TopicPermission) SetupWebhookWithManager(mgr ctrl.Manager) error { +func (t *TopicPermission) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). - For(r). + WithValidator(t). + For(t). Complete() } //+kubebuilder:webhook:path=/validate-rabbitmq-com-v1beta1-topicpermission,mutating=false,failurePolicy=fail,sideEffects=None,groups=rabbitmq.com,resources=topicpermissions,verbs=create;update,versions=v1beta1,name=vtopicpermission.kb.io,admissionReviewVersions={v1,v1beta1} -var _ webhook.Validator = &TopicPermission{} +var _ webhook.CustomValidator = &TopicPermission{} // ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (p *TopicPermission) ValidateCreate() (admission.Warnings, error) { - var errorList field.ErrorList - if p.Spec.User == "" && p.Spec.UserReference == nil { - errorList = append(errorList, field.Required(field.NewPath("spec", "user and userReference"), - "must specify either spec.user or spec.userReference")) - return nil, apierrors.NewInvalid(GroupVersion.WithKind("Permission").GroupKind(), p.Name, errorList) +func (t *TopicPermission) ValidateCreate(_ context.Context, obj runtime.Object) (warnings admission.Warnings, err error) { + tp, ok := obj.(*TopicPermission) + if !ok { + return nil, fmt.Errorf("expected a RabbitMQ permission but got a %T", obj) } - if p.Spec.User != "" && p.Spec.UserReference != nil { - errorList = append(errorList, field.Required(field.NewPath("spec", "user and userReference"), - "cannot specify spec.user and spec.userReference at the same time")) - return nil, apierrors.NewInvalid(GroupVersion.WithKind("Permission").GroupKind(), p.Name, errorList) + if tp.Spec.User == "" && tp.Spec.UserReference == nil { + return nil, field.Required(field.NewPath("spec", "user and userReference"), + "must specify either spec.user or spec.userReference") } - return p.Spec.RabbitmqClusterReference.ValidateOnCreate(p.GroupResource(), p.Name) + + if tp.Spec.User != "" && tp.Spec.UserReference != nil { + return nil, field.Required(field.NewPath("spec", "user and userReference"), + "cannot specify spec.user and spec.userReference at the same time") + } + return nil, tp.Spec.RabbitmqClusterReference.validate(tp.RabbitReference()) } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (p *TopicPermission) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - oldPermission, ok := old.(*TopicPermission) +func (t *TopicPermission) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (warnings admission.Warnings, err error) { + oldPermission, ok := oldObj.(*TopicPermission) + if !ok { + return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a permission but got a %T", oldObj)) + } + + newPermission, ok := newObj.(*TopicPermission) if !ok { - return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a permission but got a %T", old)) + return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a permission but got a %T", newObj)) } var errorList field.ErrorList - if p.Spec.User == "" && p.Spec.UserReference == nil { + if newPermission.Spec.User == "" && newPermission.Spec.UserReference == nil { errorList = append(errorList, field.Required(field.NewPath("spec", "user and userReference"), "must specify either spec.user or spec.userReference")) - return nil, apierrors.NewInvalid(GroupVersion.WithKind("TopicPermission").GroupKind(), p.Name, errorList) + return nil, apierrors.NewInvalid(GroupVersion.WithKind("TopicPermission").GroupKind(), newPermission.Name, errorList) } - if p.Spec.User != "" && p.Spec.UserReference != nil { + if newPermission.Spec.User != "" && newPermission.Spec.UserReference != nil { errorList = append(errorList, field.Required(field.NewPath("spec", "user and userReference"), "cannot specify spec.user and spec.userReference at the same time")) - return nil, apierrors.NewInvalid(GroupVersion.WithKind("TopicPermission").GroupKind(), p.Name, errorList) + return nil, apierrors.NewInvalid(GroupVersion.WithKind("TopicPermission").GroupKind(), newPermission.Name, errorList) } - detailMsg := "updates on exchange, user, userReference, vhost and rabbitmqClusterReference are all forbidden" - if p.Spec.Permissions.Exchange != oldPermission.Spec.Permissions.Exchange { - return nil, apierrors.NewForbidden(p.GroupResource(), p.Name, + const detailMsg = "updates on exchange, user, userReference, vhost and rabbitmqClusterReference are all forbidden" + if newPermission.Spec.Permissions.Exchange != oldPermission.Spec.Permissions.Exchange { + return nil, apierrors.NewForbidden(newPermission.GroupResource(), newPermission.Name, field.Forbidden(field.NewPath("spec", "permissions", "exchange"), detailMsg)) } - if p.Spec.User != oldPermission.Spec.User { - return nil, apierrors.NewForbidden(p.GroupResource(), p.Name, + if newPermission.Spec.User != oldPermission.Spec.User { + return nil, apierrors.NewForbidden(newPermission.GroupResource(), newPermission.Name, field.Forbidden(field.NewPath("spec", "user"), detailMsg)) } - if userReferenceUpdated(p.Spec.UserReference, oldPermission.Spec.UserReference) { - return nil, apierrors.NewForbidden(p.GroupResource(), p.Name, + if userReferenceUpdated(newPermission.Spec.UserReference, oldPermission.Spec.UserReference) { + return nil, apierrors.NewForbidden(newPermission.GroupResource(), newPermission.Name, field.Forbidden(field.NewPath("spec", "userReference"), detailMsg)) } - if p.Spec.Vhost != oldPermission.Spec.Vhost { - return nil, apierrors.NewForbidden(p.GroupResource(), p.Name, + if newPermission.Spec.Vhost != oldPermission.Spec.Vhost { + return nil, apierrors.NewForbidden(newPermission.GroupResource(), newPermission.Name, field.Forbidden(field.NewPath("spec", "vhost"), detailMsg)) } - if !oldPermission.Spec.RabbitmqClusterReference.Matches(&p.Spec.RabbitmqClusterReference) { - return nil, apierrors.NewForbidden(p.GroupResource(), p.Name, + if !oldPermission.Spec.RabbitmqClusterReference.Matches(&newPermission.Spec.RabbitmqClusterReference) { + return nil, apierrors.NewForbidden(newPermission.GroupResource(), newPermission.Name, field.Forbidden(field.NewPath("spec", "rabbitmqClusterReference"), detailMsg)) } return nil, nil } // ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *TopicPermission) ValidateDelete() (admission.Warnings, error) { +func (t *TopicPermission) ValidateDelete(_ context.Context, _ runtime.Object) (warnings admission.Warnings, err error) { return nil, nil } diff --git a/api/v1beta1/topicpermission_webhook_test.go b/api/v1beta1/topicpermission_webhook_test.go index ee70d62a..82693a6c 100644 --- a/api/v1beta1/topicpermission_webhook_test.go +++ b/api/v1beta1/topicpermission_webhook_test.go @@ -1,57 +1,65 @@ package v1beta1 import ( + "context" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) var _ = Describe("topic permission webhook", func() { - var permission = TopicPermission{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - }, - Spec: TopicPermissionSpec{ - User: "test-user", - Vhost: "/a-vhost", - Permissions: TopicPermissionConfig{ - Exchange: "a-exchange", - Read: ".*", - Write: ".*", + var ( + permission = TopicPermission{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", }, - RabbitmqClusterReference: RabbitmqClusterReference{ - Name: "a-cluster", + Spec: TopicPermissionSpec{ + User: "test-user", + Vhost: "/a-vhost", + Permissions: TopicPermissionConfig{ + Exchange: "a-exchange", + Read: ".*", + Write: ".*", + }, + RabbitmqClusterReference: RabbitmqClusterReference{ + Name: "a-cluster", + }, }, - }, - } + } + rootCtx = context.Background() + ) Context("ValidateCreate", func() { It("does not allow user and userReference to be specified at the same time", func() { invalidPermission := permission.DeepCopy() invalidPermission.Spec.UserReference = &corev1.LocalObjectReference{Name: "invalid"} invalidPermission.Spec.User = "test-user" - Expect(apierrors.IsInvalid(ignoreNilWarning(invalidPermission.ValidateCreate()))).To(BeTrue()) + _, err := invalidPermission.ValidateCreate(rootCtx, invalidPermission) + Expect(err).To(MatchError(ContainSubstring("cannot specify spec.user and spec.userReference at the same time"))) }) + It("does not allow both user and userReference to be unset", func() { invalidPermission := permission.DeepCopy() invalidPermission.Spec.UserReference = nil invalidPermission.Spec.User = "" - Expect(apierrors.IsInvalid(ignoreNilWarning(invalidPermission.ValidateCreate()))).To(BeTrue()) + _, err := invalidPermission.ValidateCreate(rootCtx, invalidPermission) + Expect(err).To(MatchError(ContainSubstring("must specify either spec.user or spec.userReference"))) }) It("does not allow both spec.rabbitmqClusterReference.name and spec.rabbitmqClusterReference.connectionSecret be configured", func() { notAllowed := permission.DeepCopy() notAllowed.Spec.RabbitmqClusterReference.ConnectionSecret = &corev1.LocalObjectReference{Name: "some-secret"} - Expect(apierrors.IsForbidden(ignoreNilWarning(notAllowed.ValidateCreate()))).To(BeTrue()) + _, err := notAllowed.ValidateCreate(rootCtx, notAllowed) + Expect(err).To(MatchError(ContainSubstring("invalid RabbitmqClusterReference: do not provide both name and connectionSecret"))) }) It("spec.rabbitmqClusterReference.name and spec.rabbitmqClusterReference.connectionSecret cannot both be empty", func() { notAllowed := permission.DeepCopy() notAllowed.Spec.RabbitmqClusterReference.Name = "" notAllowed.Spec.RabbitmqClusterReference.ConnectionSecret = nil - Expect(apierrors.IsForbidden(ignoreNilWarning(notAllowed.ValidateCreate()))).To(BeTrue()) + _, err := notAllowed.ValidateCreate(rootCtx, notAllowed) + Expect(err).To(MatchError(ContainSubstring("invalid RabbitmqClusterReference: must provide either name or connectionSecret"))) }) }) @@ -59,7 +67,8 @@ var _ = Describe("topic permission webhook", func() { It("does not allow updates on user", func() { newPermission := permission.DeepCopy() newPermission.Spec.User = "new-user" - Expect(apierrors.IsForbidden(ignoreNilWarning(newPermission.ValidateUpdate(&permission)))).To(BeTrue()) + _, err := newPermission.ValidateUpdate(rootCtx, &permission, newPermission) + Expect(err).To(MatchError(ContainSubstring("updates on exchange, user, userReference, vhost and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on userReference", func() { @@ -68,13 +77,15 @@ var _ = Describe("topic permission webhook", func() { permissionWithUserRef.Spec.UserReference = &corev1.LocalObjectReference{Name: "a-user"} newPermission := permissionWithUserRef.DeepCopy() newPermission.Spec.UserReference = &corev1.LocalObjectReference{Name: "a-new-user"} - Expect(apierrors.IsForbidden(ignoreNilWarning(newPermission.ValidateUpdate(permissionWithUserRef)))).To(BeTrue()) + _, err := newPermission.ValidateUpdate(rootCtx, permissionWithUserRef, newPermission) + Expect(err).To(MatchError(ContainSubstring("updates on exchange, user, userReference, vhost and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on vhost", func() { newPermission := permission.DeepCopy() newPermission.Spec.Vhost = "new-vhost" - Expect(apierrors.IsForbidden(ignoreNilWarning(newPermission.ValidateUpdate(&permission)))).To(BeTrue()) + _, err := newPermission.ValidateUpdate(rootCtx, &permission, newPermission) + Expect(err).To(MatchError(ContainSubstring("updates on exchange, user, userReference, vhost and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on RabbitmqClusterReference", func() { @@ -82,63 +93,70 @@ var _ = Describe("topic permission webhook", func() { newPermission.Spec.RabbitmqClusterReference = RabbitmqClusterReference{ Name: "new-cluster", } - Expect(apierrors.IsForbidden(ignoreNilWarning(newPermission.ValidateUpdate(&permission)))).To(BeTrue()) + _, err := newPermission.ValidateUpdate(rootCtx, &permission, newPermission) + Expect(err).To(MatchError(ContainSubstring("updates on exchange, user, userReference, vhost and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on rabbitmqClusterReference.connectionSecret", func() { - connectionScr := Permission{ + connectionScr := TopicPermission{ ObjectMeta: metav1.ObjectMeta{ Name: "test", }, - Spec: PermissionSpec{ + Spec: TopicPermissionSpec{ User: "test-user", Vhost: "/a-vhost", - Permissions: VhostPermissions{ - Configure: ".*", - Read: ".*", - Write: ".*", + Permissions: TopicPermissionConfig{ + Exchange: "a-exchange", + Read: ".*", + Write: ".*", }, RabbitmqClusterReference: RabbitmqClusterReference{ - ConnectionSecret: &corev1.LocalObjectReference{ - Name: "a-secret", - }, + ConnectionSecret: &corev1.LocalObjectReference{Name: "some-secret"}, }, }, } - new := connectionScr.DeepCopy() - new.Spec.RabbitmqClusterReference.ConnectionSecret.Name = "new-secret" - Expect(apierrors.IsForbidden(ignoreNilWarning(new.ValidateUpdate(&connectionScr)))).To(BeTrue()) + + newTopicPermissions := connectionScr.DeepCopy() + Expect(newTopicPermissions).ToNot(BeNil()) + newTopicPermissions.Spec.RabbitmqClusterReference.ConnectionSecret.Name = "new-secret" + _, err := newTopicPermissions.ValidateUpdate(rootCtx, &connectionScr, newTopicPermissions) + Expect(err).To(MatchError(ContainSubstring("updates on exchange, user, userReference, vhost and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on spec.permissions.exchange", func() { newPermission := permission.DeepCopy() newPermission.Spec.Permissions.Exchange = "a-different-exchange" - Expect(apierrors.IsForbidden(ignoreNilWarning(newPermission.ValidateUpdate(&permission)))).To(BeTrue()) + _, err := newPermission.ValidateUpdate(rootCtx, &permission, newPermission) + Expect(err).To(MatchError(ContainSubstring("updates on exchange, user, userReference, vhost and rabbitmqClusterReference are all forbidden"))) }) It("allows updates on permission.spec.permissions.read", func() { newPermission := permission.DeepCopy() newPermission.Spec.Permissions.Read = "?" - Expect(ignoreNilWarning(newPermission.ValidateUpdate(&permission))).To(Succeed()) + _, err := newPermission.ValidateUpdate(rootCtx, &permission, newPermission) + Expect(err).ToNot(HaveOccurred()) }) It("allows updates on permission.spec.permissions.write", func() { newPermission := permission.DeepCopy() newPermission.Spec.Permissions.Write = "?" - Expect(ignoreNilWarning(newPermission.ValidateUpdate(&permission))).To(Succeed()) + _, err := newPermission.ValidateUpdate(rootCtx, &permission, newPermission) + Expect(err).ToNot(HaveOccurred()) }) It("does not allow user and userReference to be specified at the same time", func() { newPermission := permission.DeepCopy() newPermission.Spec.UserReference = &corev1.LocalObjectReference{Name: "invalid"} - Expect(apierrors.IsInvalid(ignoreNilWarning(newPermission.ValidateUpdate(&permission)))).To(BeTrue()) + _, err := newPermission.ValidateUpdate(rootCtx, &permission, newPermission) + Expect(err).To(MatchError(ContainSubstring("cannot specify spec.user and spec.userReference at the same time"))) }) It("does not allow both user and userReference to be unset", func() { newPermission := permission.DeepCopy() newPermission.Spec.User = "" newPermission.Spec.UserReference = nil - Expect(apierrors.IsInvalid(ignoreNilWarning(newPermission.ValidateUpdate(&permission)))).To(BeTrue()) + _, err := newPermission.ValidateUpdate(rootCtx, &permission, newPermission) + Expect(err).To(MatchError(ContainSubstring("must specify either spec.user or spec.userReference"))) }) }) }) diff --git a/api/v1beta1/user_webhook.go b/api/v1beta1/user_webhook.go index 04315d6e..1c2d90c4 100644 --- a/api/v1beta1/user_webhook.go +++ b/api/v1beta1/user_webhook.go @@ -1,6 +1,7 @@ package v1beta1 import ( + "context" "fmt" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -12,35 +13,44 @@ import ( func (u *User) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). + WithValidator(u). For(u). Complete() } // +kubebuilder:webhook:verbs=create;update,path=/validate-rabbitmq-com-v1beta1-user,mutating=false,failurePolicy=fail,groups=rabbitmq.com,resources=users,versions=v1beta1,name=vuser.kb.io,sideEffects=none,admissionReviewVersions=v1 -var _ webhook.Validator = &User{} +var _ webhook.CustomValidator = &User{} -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type -// either rabbitmqClusterReference.name or rabbitmqClusterReference.connectionSecret must be provided but not both -func (u *User) ValidateCreate() (admission.Warnings, error) { - return u.Spec.RabbitmqClusterReference.ValidateOnCreate(u.GroupResource(), u.Name) +// ValidateCreate - either rabbitmqClusterReference.name or +// rabbitmqClusterReference.connectionSecret must be provided but not both +func (u *User) ValidateCreate(_ context.Context, obj runtime.Object) (warnings admission.Warnings, err error) { + user, ok := obj.(*User) + if !ok { + return nil, fmt.Errorf("expected a RabbitMQ user but got a %T", obj) + } + return nil, u.Spec.RabbitmqClusterReference.validate(user.RabbitReference()) } // ValidateUpdate returns error type 'forbidden' for updates on rabbitmqClusterReference -// user.spec.tags can be updated -func (u *User) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - oldUser, ok := old.(*User) +func (u *User) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (warnings admission.Warnings, err error) { + oldUser, ok := oldObj.(*User) + if !ok { + return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a user but got a %T", oldObj)) + } + + newUser, ok := newObj.(*User) if !ok { - return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a user but got a %T", old)) + return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a user but got a %T", newObj)) } - if !oldUser.Spec.RabbitmqClusterReference.Matches(&u.Spec.RabbitmqClusterReference) { - return nil, apierrors.NewForbidden(u.GroupResource(), u.Name, + if !oldUser.Spec.RabbitmqClusterReference.Matches(&newUser.Spec.RabbitmqClusterReference) { + return nil, apierrors.NewForbidden(newUser.GroupResource(), newUser.Name, field.Forbidden(field.NewPath("spec", "rabbitmqClusterReference"), "update on rabbitmqClusterReference is forbidden")) } return nil, nil } -func (u *User) ValidateDelete() (admission.Warnings, error) { +func (u *User) ValidateDelete(_ context.Context, obj runtime.Object) (warnings admission.Warnings, err error) { return nil, nil } diff --git a/api/v1beta1/user_webhook_test.go b/api/v1beta1/user_webhook_test.go index f67c1b87..c7194ab7 100644 --- a/api/v1beta1/user_webhook_test.go +++ b/api/v1beta1/user_webhook_test.go @@ -1,38 +1,43 @@ package v1beta1 import ( + "context" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) var _ = Describe("user webhook", func() { - var user = User{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - }, - Spec: UserSpec{ - Tags: []UserTag{"policymaker"}, - RabbitmqClusterReference: RabbitmqClusterReference{ - Name: "a-cluster", + var ( + user = User{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", }, - }, - } + Spec: UserSpec{ + Tags: []UserTag{"policymaker"}, + RabbitmqClusterReference: RabbitmqClusterReference{ + Name: "a-cluster", + }, + }, + } + rootCtx = context.Background() + ) Context("ValidateCreate", func() { It("does not allow both spec.rabbitmqClusterReference.name and spec.rabbitmqClusterReference.connectionSecret be configured", func() { notAllowed := user.DeepCopy() notAllowed.Spec.RabbitmqClusterReference.ConnectionSecret = &corev1.LocalObjectReference{Name: "some-secret"} - Expect(apierrors.IsForbidden(ignoreNilWarning(notAllowed.ValidateCreate()))).To(BeTrue()) + _, err := notAllowed.ValidateCreate(rootCtx, notAllowed) + Expect(err).To(MatchError(ContainSubstring("invalid RabbitmqClusterReference: do not provide both name and connectionSecret"))) }) It("spec.rabbitmqClusterReference.name and spec.rabbitmqClusterReference.connectionSecret cannot both be empty", func() { notAllowed := user.DeepCopy() notAllowed.Spec.RabbitmqClusterReference.Name = "" notAllowed.Spec.RabbitmqClusterReference.ConnectionSecret = nil - Expect(apierrors.IsForbidden(ignoreNilWarning(notAllowed.ValidateCreate()))).To(BeTrue()) + _, err := notAllowed.ValidateCreate(rootCtx, notAllowed) + Expect(err).To(MatchError(ContainSubstring("invalid RabbitmqClusterReference: must provide either name or connectionSecret"))) }) }) @@ -42,7 +47,8 @@ var _ = Describe("user webhook", func() { newUser.Spec.RabbitmqClusterReference = RabbitmqClusterReference{ Name: "newUser-cluster", } - Expect(apierrors.IsForbidden(ignoreNilWarning(newUser.ValidateUpdate(&user)))).To(BeTrue()) + _, err := newUser.ValidateUpdate(rootCtx, &user, newUser) + Expect(err).To(MatchError(ContainSubstring("update on rabbitmqClusterReference is forbidden"))) }) It("does not allow updates on rabbitmqClusterReference.connectionSecret", func() { @@ -59,16 +65,18 @@ var _ = Describe("user webhook", func() { }, }, } - new := connectionScr.DeepCopy() - new.Spec.RabbitmqClusterReference.Name = "a-name" - new.Spec.RabbitmqClusterReference.ConnectionSecret = nil - Expect(apierrors.IsForbidden(ignoreNilWarning(new.ValidateUpdate(&connectionScr)))).To(BeTrue()) + newUser := connectionScr.DeepCopy() + newUser.Spec.RabbitmqClusterReference.Name = "a-name" + newUser.Spec.RabbitmqClusterReference.ConnectionSecret = nil + _, err := newUser.ValidateUpdate(rootCtx, &connectionScr, newUser) + Expect(err).To(MatchError(ContainSubstring("update on rabbitmqClusterReference is forbidden"))) }) It("allows update on tags", func() { newUser := user.DeepCopy() newUser.Spec.Tags = []UserTag{"monitoring"} - Expect(ignoreNilWarning(newUser.ValidateUpdate(&user))).To(Succeed()) + _, err := newUser.ValidateUpdate(rootCtx, &user, newUser) + Expect(err).ToNot(HaveOccurred()) }) }) }) diff --git a/api/v1beta1/vhost_webhook.go b/api/v1beta1/vhost_webhook.go index 67509a0c..6ec48b2e 100644 --- a/api/v1beta1/vhost_webhook.go +++ b/api/v1beta1/vhost_webhook.go @@ -1,6 +1,7 @@ package v1beta1 import ( + "context" "fmt" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -10,44 +11,55 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) -func (r *Vhost) SetupWebhookWithManager(mgr ctrl.Manager) error { +func (v *Vhost) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). - For(r). + WithValidator(v). + For(v). Complete() } // +kubebuilder:webhook:verbs=create;update,path=/validate-rabbitmq-com-v1beta1-vhost,mutating=false,failurePolicy=fail,groups=rabbitmq.com,resources=vhosts,versions=v1beta1,name=vvhost.kb.io,sideEffects=none,admissionReviewVersions=v1 -var _ webhook.Validator = &Vhost{} +var _ webhook.CustomValidator = &Vhost{} -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type -// either rabbitmqClusterReference.name or rabbitmqClusterReference.connectionSecret must be provided but not both -func (v *Vhost) ValidateCreate() (admission.Warnings, error) { - return v.Spec.RabbitmqClusterReference.ValidateOnCreate(v.GroupResource(), v.Name) +// ValidateCreate +// +// Either rabbitmqClusterReference.name or +// rabbitmqClusterReference.connectionSecret must be provided but not both +func (v *Vhost) ValidateCreate(_ context.Context, obj runtime.Object) (warnings admission.Warnings, err error) { + vhost, ok := obj.(*Vhost) + if !ok { + return nil, fmt.Errorf("expected a RabbitMQ vhost but got a %T", obj) + } + return nil, v.Spec.RabbitmqClusterReference.validate(vhost.RabbitReference()) } // ValidateUpdate returns error type 'forbidden' for updates on vhost name and rabbitmqClusterReference -// vhost.spec.tracing can be updated -func (v *Vhost) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - oldVhost, ok := old.(*Vhost) +func (v *Vhost) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (warnings admission.Warnings, err error) { + oldVhost, ok := oldObj.(*Vhost) + if !ok { + return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a vhost but got a %T", oldObj)) + } + + newVhost, ok := newObj.(*Vhost) if !ok { - return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a vhost but got a %T", old)) + return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a vhost but got a %T", newObj)) } - detailMsg := "updates on name and rabbitmqClusterReference are all forbidden" - if v.Spec.Name != oldVhost.Spec.Name { - return nil, apierrors.NewForbidden(v.GroupResource(), v.Name, + const detailMsg = "updates on name and rabbitmqClusterReference are all forbidden" + if newVhost.Spec.Name != oldVhost.Spec.Name { + return nil, apierrors.NewForbidden(newVhost.GroupResource(), newVhost.Name, field.Forbidden(field.NewPath("spec", "name"), detailMsg)) } - if !oldVhost.Spec.RabbitmqClusterReference.Matches(&v.Spec.RabbitmqClusterReference) { - return nil, apierrors.NewForbidden(v.GroupResource(), v.Name, + if !oldVhost.Spec.RabbitmqClusterReference.Matches(&newVhost.Spec.RabbitmqClusterReference) { + return nil, apierrors.NewForbidden(newVhost.GroupResource(), newVhost.Name, field.Forbidden(field.NewPath("spec", "rabbitmqClusterReference"), detailMsg)) } return nil, nil } -func (v *Vhost) ValidateDelete() (admission.Warnings, error) { +func (v *Vhost) ValidateDelete(_ context.Context, obj runtime.Object) (warnings admission.Warnings, err error) { return nil, nil } diff --git a/api/v1beta1/vhost_webhook_test.go b/api/v1beta1/vhost_webhook_test.go index 2ff6db64..3390ab3e 100644 --- a/api/v1beta1/vhost_webhook_test.go +++ b/api/v1beta1/vhost_webhook_test.go @@ -1,41 +1,46 @@ package v1beta1 import ( + "context" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) var _ = Describe("vhost webhook", func() { - var vhost = Vhost{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-vhost", - }, - Spec: VhostSpec{ - Name: "test", - Tracing: false, - DefaultQueueType: "classic", - RabbitmqClusterReference: RabbitmqClusterReference{ - Name: "a-cluster", + var ( + vhost = Vhost{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-vhost", }, - }, - } + Spec: VhostSpec{ + Name: "test", + Tracing: false, + DefaultQueueType: "classic", + RabbitmqClusterReference: RabbitmqClusterReference{ + Name: "a-cluster", + }, + }, + } + rootCtx = context.Background() + ) Context("ValidateCreate", func() { It("does not allow both spec.rabbitmqClusterReference.name and spec.rabbitmqClusterReference.connectionSecret be configured", func() { notAllowed := vhost.DeepCopy() notAllowed.Spec.RabbitmqClusterReference.ConnectionSecret = &corev1.LocalObjectReference{Name: "some-secret"} - Expect(apierrors.IsForbidden(ignoreNilWarning(notAllowed.ValidateCreate()))).To(BeTrue()) + _, err := notAllowed.ValidateCreate(rootCtx, notAllowed) + Expect(err).To(MatchError(ContainSubstring("invalid RabbitmqClusterReference: do not provide both name and connectionSecret"))) }) It("spec.rabbitmqClusterReference.name and spec.rabbitmqClusterReference.connectionSecret cannot both be empty", func() { notAllowed := vhost.DeepCopy() notAllowed.Spec.RabbitmqClusterReference.Name = "" notAllowed.Spec.RabbitmqClusterReference.ConnectionSecret = nil - Expect(apierrors.IsForbidden(ignoreNilWarning(notAllowed.ValidateCreate()))).To(BeTrue()) + _, err := notAllowed.ValidateCreate(rootCtx, notAllowed) + Expect(err).To(MatchError(ContainSubstring("invalid RabbitmqClusterReference: must provide either name or connectionSecret"))) }) }) @@ -43,7 +48,8 @@ var _ = Describe("vhost webhook", func() { It("does not allow updates on vhost name", func() { newVhost := vhost.DeepCopy() newVhost.Spec.Name = "new-name" - Expect(apierrors.IsForbidden(ignoreNilWarning(newVhost.ValidateUpdate(&vhost)))).To(BeTrue()) + _, err := newVhost.ValidateUpdate(rootCtx, &vhost, newVhost) + Expect(err).To(MatchError(ContainSubstring("updates on name and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on RabbitmqClusterReference", func() { @@ -51,7 +57,8 @@ var _ = Describe("vhost webhook", func() { newVhost.Spec.RabbitmqClusterReference = RabbitmqClusterReference{ Name: "new-cluster", } - Expect(apierrors.IsForbidden(ignoreNilWarning(newVhost.ValidateUpdate(&vhost)))).To(BeTrue()) + _, err := newVhost.ValidateUpdate(rootCtx, &vhost, newVhost) + Expect(err).To(MatchError(ContainSubstring("updates on name and rabbitmqClusterReference are all forbidden"))) }) It("does not allow updates on rabbitmqClusterReference.connectionSecret", func() { @@ -68,28 +75,32 @@ var _ = Describe("vhost webhook", func() { }, }, } - new := connectionScr.DeepCopy() - new.Spec.RabbitmqClusterReference.Name = "a-name" - new.Spec.RabbitmqClusterReference.ConnectionSecret = nil - Expect(apierrors.IsForbidden(ignoreNilWarning(new.ValidateUpdate(&connectionScr)))).To(BeTrue()) + newVhost := connectionScr.DeepCopy() + newVhost.Spec.RabbitmqClusterReference.Name = "a-name" + newVhost.Spec.RabbitmqClusterReference.ConnectionSecret = nil + _, err := newVhost.ValidateUpdate(rootCtx, &connectionScr, newVhost) + Expect(err).To(MatchError(ContainSubstring("updates on name and rabbitmqClusterReference are all forbidden"))) }) It("allows updates on vhost.spec.tracing", func() { newVhost := vhost.DeepCopy() newVhost.Spec.Tracing = true - Expect(ignoreNilWarning(newVhost.ValidateUpdate(&vhost))).To(Succeed()) + _, err := newVhost.ValidateUpdate(rootCtx, &vhost, newVhost) + Expect(err).ToNot(HaveOccurred()) }) It("allows updates on vhost.spec.tags", func() { newVhost := vhost.DeepCopy() newVhost.Spec.Tags = []string{"new-tag"} - Expect(ignoreNilWarning(newVhost.ValidateUpdate(&vhost))).To(Succeed()) + _, err := newVhost.ValidateUpdate(rootCtx, &vhost, newVhost) + Expect(err).ToNot(HaveOccurred()) }) It("allows updates on vhost.spec.defaultQueueType", func() { newVhost := vhost.DeepCopy() newVhost.Spec.DefaultQueueType = "quorum" - Expect(ignoreNilWarning(newVhost.ValidateUpdate(&vhost))).To(Succeed()) + _, err := newVhost.ValidateUpdate(rootCtx, &vhost, newVhost) + Expect(err).ToNot(HaveOccurred()) }) }) }) diff --git a/go.mod b/go.mod index 7e844a03..e4abd31a 100644 --- a/go.mod +++ b/go.mod @@ -22,9 +22,9 @@ require ( k8s.io/client-go v0.29.1 k8s.io/code-generator v0.29.1 k8s.io/klog/v2 v2.120.1 - k8s.io/kube-openapi v0.0.0-20240105020646-a37d4de58910 + k8s.io/kube-openapi v0.0.0-20240126223410-2919ad4fcfec k8s.io/utils v0.0.0-20240102154912-e7106e64919e - sigs.k8s.io/controller-runtime v0.16.3 + sigs.k8s.io/controller-runtime v0.17.0 sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20231121004636-2154ffbc22e2 sigs.k8s.io/controller-tools v0.14.0 sigs.k8s.io/kustomize/kustomize/v5 v5.3.0 @@ -35,12 +35,12 @@ require ( github.com/Masterminds/semver v1.5.0 // indirect github.com/Masterminds/sprig v2.22.0+incompatible // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/cenkalti/backoff/v3 v3.0.0 // indirect + github.com/cenkalti/backoff/v3 v3.2.2 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/emicklei/go-restful/v3 v3.11.1 // indirect - github.com/evanphx/json-patch v5.7.0+incompatible // indirect - github.com/evanphx/json-patch/v5 v5.7.0 // indirect + github.com/emicklei/go-restful/v3 v3.11.2 // indirect + github.com/evanphx/json-patch v5.9.0+incompatible // indirect + github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/fatih/color v1.16.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-errors/errors v1.5.0 // indirect @@ -48,7 +48,7 @@ require ( github.com/go-logr/zapr v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.20.2 // indirect github.com/go-openapi/jsonreference v0.20.4 // indirect - github.com/go-openapi/swag v0.22.7 // indirect + github.com/go-openapi/swag v0.22.9 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/gobuffalo/flect v1.0.2 // indirect github.com/goccy/go-yaml v1.11.2 // indirect @@ -65,11 +65,11 @@ require ( github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v1.0.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-retryablehttp v0.6.6 // indirect + github.com/hashicorp/go-retryablehttp v0.7.5 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect - github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect + github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect - github.com/hashicorp/go-sockaddr v1.0.2 // indirect + github.com/hashicorp/go-sockaddr v1.0.6 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/huandu/xstrings v1.3.3 // indirect github.com/imdario/mergo v0.3.16 // indirect @@ -80,7 +80,6 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect @@ -93,7 +92,7 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.18.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.45.0 // indirect + github.com/prometheus/common v0.46.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect github.com/spf13/afero v1.6.0 // indirect @@ -107,10 +106,10 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/crypto v0.18.0 // indirect - golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect + golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/net v0.20.0 // indirect - golang.org/x/oauth2 v0.15.0 // indirect + golang.org/x/oauth2 v0.16.0 // indirect golang.org/x/sys v0.16.0 // indirect golang.org/x/term v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect @@ -124,8 +123,8 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.29.0 // indirect - k8s.io/component-base v0.29.0 // indirect + k8s.io/apiextensions-apiserver v0.29.1 // indirect + k8s.io/component-base v0.29.1 // indirect k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/kustomize/api v0.16.0 // indirect diff --git a/go.sum b/go.sum index c0bd7a5a..a17df0ae 100644 --- a/go.sum +++ b/go.sum @@ -8,13 +8,11 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0 github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 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/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= -github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= +github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= +github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -31,14 +29,14 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/elastic/crd-ref-docs v0.0.10 h1:FAc9oCxxY4+rMCLSLtTGrEaPyuxmp3LNlQ+dZfG9Ujc= github.com/elastic/crd-ref-docs v0.0.10/go.mod h1:zha4djxzWirfx+c4fl/Kmk9Rc7Fv7XBoOi9CL9kne+M= -github.com/emicklei/go-restful/v3 v3.11.1 h1:S+9bSbua1z3FgCnV0KKOSSZ3mDthb5NyEPL5gEpCvyk= -github.com/emicklei/go-restful/v3 v3.11.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.11.2 h1:1onLa9DcsMYO9P+CXaL0dStDqQ2EHHXLiz+BtnqkLAU= +github.com/emicklei/go-restful/v3 v3.11.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= -github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch/v5 v5.7.0 h1:nJqP7uwL84RJInrohHfW0Fx3awjbm8qZeFv0nW9SYGc= -github.com/evanphx/json-patch/v5 v5.7.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +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/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= @@ -62,16 +60,20 @@ github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbX github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU= github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4= -github.com/go-openapi/swag v0.22.7 h1:JWrc1uc/P9cSomxfnsFSVWoE1FW6bNbrVPmpQYpCcR8= -github.com/go-openapi/swag v0.22.7/go.mod h1:Gl91UqO+btAM0plGGxHqJcQZ1ZTy6jbmridBTsDy8A0= +github.com/go-openapi/swag v0.22.9 h1:XX2DssF+mQKM2DHsbgZK74y/zj4mo9I99+89xUmuZCE= +github.com/go-openapi/swag v0.22.9/go.mod h1:3/OXnFfnMAwBD099SwYRk7GD3xOrr1iL7d/XNLXVVwE= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= +github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA= github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= github.com/goccy/go-yaml v1.11.2 h1:joq77SxuyIs9zzxEjgyLBugMQ9NEgTWxXfz2wVqwAaQ= @@ -128,26 +130,23 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v1.0.0 h1:bkKf0BeBXcSYa7f5Fyi9gMuQ8gNsxeiNpZjR6VxNZeo= github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM= -github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= +github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 h1:om4Al8Oy7kCm/B86rLCLah4Dt5Aa0Fr5rYBG60OzwHQ= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= -github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 h1:iBt4Ew4XEGLfh6/bPk4rSYmuZJGizr6/x/AEizP0CQc= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8/go.mod h1:aiJI+PIApBRQG7FZTEBx5GiiX+HbOHilUdNxUZi4eV0= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= -github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= -github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= +github.com/hashicorp/go-sockaddr v1.0.6 h1:RSG8rKU28VTUTvEKghe5gIhIQpv8evvNpnDEyqO4u9I= +github.com/hashicorp/go-sockaddr v1.0.6/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI= 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/hashicorp/vault/api v1.11.0 h1:AChWByeHf4/P9sX3Y1B7vFsQhZO2BgQiCMQ2SA1P1UY= @@ -174,37 +173,33 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 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/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/maxbrunsfeld/counterfeiter/v6 v6.8.1 h1:NicmruxkeqHjDv03SfSxqmaLuisddudfP3h5wdXFbhM= github.com/maxbrunsfeld/counterfeiter/v6 v6.8.1/go.mod h1:eyp4DdUJAKkr9tvxR3jWhw2mDK7CWABMG5r9uyaKC7I= github.com/michaelklishin/rabbit-hole/v2 v2.16.0 h1:RvTPW3DnmyR7R1XTbET0ILRneU1Ou3vzIVj4YHBIE/g= github.com/michaelklishin/rabbit-hole/v2 v2.16.0/go.mod h1:wnHAhXYVncuXKdF/3mdywRE4vzBgn4k07Z+HjdNGMpM= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 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/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= @@ -277,14 +272,13 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= -github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= +github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y= +github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc3Aoo= @@ -292,14 +286,16 @@ github.com/rabbitmq/amqp091-go v1.9.0/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv github.com/rabbitmq/cluster-operator/v2 v2.7.0 h1:DqyMucZrzD31hkAnznEiAf1W+KWC0sRdu6bDTtLp7LY= github.com/rabbitmq/cluster-operator/v2 v2.7.0/go.mod h1:i4J22uZ+nGAPDLtORnF/yfdIL2tQp+bUN+RfY6qj20U= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8= +github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM= github.com/sclevine/yj v0.0.0-20200815061347-554173e71934 h1:HQgRgQK9d7cDKkRTS9zlvd6agG3yg7E4Q1ChdHgPs6Y= github.com/sclevine/yj v0.0.0-20200815061347-554173e71934/go.mod h1:AeGluipFgaqcTzUkgIazjEUPD+xbrS9qmLUE1TO1xpo= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= @@ -324,6 +320,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/weppos/publicsuffix-go v0.13.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k= github.com/weppos/publicsuffix-go v0.30.2-0.20230730094716-a20f9abcc222 h1:h2JizvZl9aIj6za9S5AyrkU+OzIS4CetQthH/ejO+lg= github.com/weppos/publicsuffix-go v0.30.2-0.20230730094716-a20f9abcc222/go.mod h1:s41lQh6dIsDWIC1OWh7ChWJXLH0zkJ9KHZVqA7vHyuQ= @@ -348,6 +345,7 @@ go.starlark.net v0.0.0-20230912135651-745481cf39ed h1:kNt8RXSIU6IRBO9MP3m+6q3Wpy go.starlark.net v0.0.0-20230912135651-745481cf39ed/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= @@ -371,8 +369,8 @@ golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM= -golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= +golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA= +golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -425,8 +423,8 @@ golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= -golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= -golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= +golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= +golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -439,7 +437,7 @@ golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -607,27 +605,27 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.29.1 h1:DAjwWX/9YT7NQD4INu49ROJuZAAAP/Ijki48GUPzxqw= k8s.io/api v0.29.1/go.mod h1:7Kl10vBRUXhnQQI8YR/R327zXC8eJ7887/+Ybta+RoQ= -k8s.io/apiextensions-apiserver v0.29.0 h1:0VuspFG7Hj+SxyF/Z/2T0uFbI5gb5LRgEyUVE3Q4lV0= -k8s.io/apiextensions-apiserver v0.29.0/go.mod h1:TKmpy3bTS0mr9pylH0nOt/QzQRrW7/h7yLdRForMZwc= +k8s.io/apiextensions-apiserver v0.29.1 h1:S9xOtyk9M3Sk1tIpQMu9wXHm5O2MX6Y1kIpPMimZBZw= +k8s.io/apiextensions-apiserver v0.29.1/go.mod h1:zZECpujY5yTW58co8V2EQR4BD6A9pktVgHhvc0uLfeU= k8s.io/apimachinery v0.29.1 h1:KY4/E6km/wLBguvCZv8cKTeOwwOBqFNjwJIdMkMbbRc= k8s.io/apimachinery v0.29.1/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= k8s.io/client-go v0.29.1 h1:19B/+2NGEwnFLzt0uB5kNJnfTsbV8w6TgQRz9l7ti7A= k8s.io/client-go v0.29.1/go.mod h1:TDG/psL9hdet0TI9mGyHJSgRkW3H9JZk2dNEUS7bRks= k8s.io/code-generator v0.29.1 h1:8ba8BdtSmAVHgAMpzThb/fuyQeTRtN7NtN7VjMcDLew= k8s.io/code-generator v0.29.1/go.mod h1:FwFi3C9jCrmbPjekhaCYcYG1n07CYiW1+PAPCockaos= -k8s.io/component-base v0.29.0 h1:T7rjd5wvLnPBV1vC4zWd/iWRbV8Mdxs+nGaoaFzGw3s= -k8s.io/component-base v0.29.0/go.mod h1:sADonFTQ9Zc9yFLghpDpmNXEdHyQmFIGbiuZbqAXQ1M= +k8s.io/component-base v0.29.1 h1:MUimqJPCRnnHsskTTjKD+IC1EHBbRCVyi37IoFBrkYw= +k8s.io/component-base v0.29.1/go.mod h1:fP9GFjxYrLERq1GcWWZAE3bqbNcDKDytn2srWuHTtKc= k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 h1:pWEwq4Asjm4vjW7vcsmijwBhOr1/shsbSYiWXmNGlks= k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20240105020646-a37d4de58910 h1:1Rp/XEKP5uxPs6QrsngEHAxBjaAR78iJRiJq5Fi7LSU= -k8s.io/kube-openapi v0.0.0-20240105020646-a37d4de58910/go.mod h1:Pa1PvrP7ACSkuX6I7KYomY6cmMA0Tx86waBhDUgoKPw= +k8s.io/kube-openapi v0.0.0-20240126223410-2919ad4fcfec h1:iGTel2aR8vCZdxJDgmbeY0zrlXy9Qcvyw4R2sB4HLrA= +k8s.io/kube-openapi v0.0.0-20240126223410-2919ad4fcfec/go.mod h1:Pa1PvrP7ACSkuX6I7KYomY6cmMA0Tx86waBhDUgoKPw= k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCfRziVtos3ofG/sQ= k8s.io/utils v0.0.0-20240102154912-e7106e64919e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4= -sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0= +sigs.k8s.io/controller-runtime v0.17.0 h1:fjJQf8Ukya+VjogLO6/bNX9HE6Y2xpsO5+fyS26ur/s= +sigs.k8s.io/controller-runtime v0.17.0/go.mod h1:+MngTvIQQQhfXtwfdGw/UOQ/aIaqsYywfCINOtwMO/s= sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20231121004636-2154ffbc22e2 h1:S1i08nqcuJsCyUBR0jvmKnMLHEbPb74yrVfUoSeuGew= sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20231121004636-2154ffbc22e2/go.mod h1:TF/lVLWS+JNNaVqJuDDictY2hZSXSsIHCx4FClMvqFg= sigs.k8s.io/controller-tools v0.14.0 h1:rnNoCC5wSXlrNoBKKzL70LNJKIQKEzT6lloG6/LF73A= diff --git a/internal/managedresource/managedresource_builder.go b/internal/managedresource/managedresource_builder.go index 7f9cd380..94cd9e25 100644 --- a/internal/managedresource/managedresource_builder.go +++ b/internal/managedresource/managedresource_builder.go @@ -22,6 +22,6 @@ type ResourceBuilder interface { ResourceType() string } -func (builder Builder) GenerateChildResourceName(suffix string) string { +func (builder *Builder) GenerateChildResourceName(suffix string) string { return builder.ObjectOwner.GetName() + suffix } diff --git a/system_tests/binding_system_test.go b/system_tests/binding_system_test.go index f8671c28..bae11e04 100644 --- a/system_tests/binding_system_test.go +++ b/system_tests/binding_system_test.go @@ -131,7 +131,7 @@ var _ = Describe("Binding", func() { updateBinding := topology.Binding{} Expect(k8sClient.Get(ctx, types.NamespacedName{Name: binding.Name, Namespace: binding.Namespace}, &updateBinding)).To(Succeed()) updatedBinding.Spec.RoutingKey = "new-key" - Expect(k8sClient.Update(ctx, &updatedBinding).Error()).To(ContainSubstring("invalid: spec.routingKey: Invalid value: \"new-key\": routingKey cannot be updated")) + Expect(k8sClient.Update(ctx, &updatedBinding).Error()).To(ContainSubstring("spec.routingKey: Invalid value: \"new-key\": routingKey cannot be updated")) By("deleting binding from rabbitmq server") Expect(k8sClient.Delete(ctx, binding)).To(Succeed())