Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update controller runtime 0.17 #756

Merged
merged 4 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions api/v1alpha1/superstream_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}

Expand Down
50 changes: 30 additions & 20 deletions api/v1alpha1/superstream_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -27,51 +28,60 @@ 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"))
}

return nil, nil
}

// 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
}

Expand Down
58 changes: 35 additions & 23 deletions api/v1alpha1/superstream_webhook_test.go
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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{
Expand All @@ -31,84 +36,91 @@ 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")))
})
})

Context("ValidateUpdate", 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() {
newSuperStream := superstream.DeepCopy()
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")))
})
})
})
Loading
Loading