From 47d5c39eb8595ab67b84907c7d4d44699eac4ea6 Mon Sep 17 00:00:00 2001 From: Chunyi Lyu Date: Tue, 9 Mar 2021 16:17:54 +0000 Subject: [PATCH 1/3] Add policies.rabbitmq.com - support create, update and delete --- PROJECT | 3 + api/v1alpha1/policy_types.go | 62 ++++++++ api/v1alpha1/policy_types_test.go | 121 +++++++++++++++ api/v1alpha1/zz_generated.deepcopy.go | 95 ++++++++++++ config/crd/bases/rabbitmq.com_bindings.yaml | 11 +- config/crd/bases/rabbitmq.com_exchanges.yaml | 11 +- config/crd/bases/rabbitmq.com_policies.yaml | 95 ++++++++++++ config/crd/bases/rabbitmq.com_queues.yaml | 17 +- config/crd/bases/rabbitmq.com_users.yaml | 27 +++- config/crd/bases/rabbitmq.com_vhosts.yaml | 13 +- config/crd/kustomization.yaml | 1 + .../crd/patches/cainjection_in_policies.yaml | 8 + config/crd/patches/webhook_in_policies.yaml | 17 ++ config/rbac/policy_editor_role.yaml | 24 +++ config/rbac/policy_viewer_role.yaml | 20 +++ config/rbac/role.yaml | 20 +++ .../samples/rabbitmq.com_v1alpha1_policy.yaml | 7 + controllers/policy_controller.go | 146 ++++++++++++++++++ docs/examples/policies/policy.yaml | 14 ++ internal/policy.go | 35 +++++ internal/policy_test.go | 70 +++++++++ main.go | 16 +- system_tests/policy_system_tests.go | 93 +++++++++++ 23 files changed, 901 insertions(+), 25 deletions(-) create mode 100644 api/v1alpha1/policy_types.go create mode 100644 api/v1alpha1/policy_types_test.go create mode 100644 config/crd/bases/rabbitmq.com_policies.yaml create mode 100644 config/crd/patches/cainjection_in_policies.yaml create mode 100644 config/crd/patches/webhook_in_policies.yaml create mode 100644 config/rbac/policy_editor_role.yaml create mode 100644 config/rbac/policy_viewer_role.yaml create mode 100644 config/samples/rabbitmq.com_v1alpha1_policy.yaml create mode 100644 controllers/policy_controller.go create mode 100644 docs/examples/policies/policy.yaml create mode 100644 internal/policy.go create mode 100644 internal/policy_test.go create mode 100644 system_tests/policy_system_tests.go diff --git a/PROJECT b/PROJECT index e942abb9..ef11e6b2 100644 --- a/PROJECT +++ b/PROJECT @@ -16,4 +16,7 @@ resources: - group: rabbitmq.com kind: Vhost version: v1alpha1 +- group: rabbitmq.com + kind: Policy + version: v1alpha1 version: "2" diff --git a/api/v1alpha1/policy_types.go b/api/v1alpha1/policy_types.go new file mode 100644 index 00000000..0b33fd63 --- /dev/null +++ b/api/v1alpha1/policy_types.go @@ -0,0 +1,62 @@ +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// PolicySpec defines the desired state of Policy +type PolicySpec struct { + // +kubebuilder:validation:Required + Name string `json:"name"` + // Default to vhost '/' + // +kubebuilder:default:=/ + Vhost string `json:"vhost,omitempty"` + // Regular expression pattern used to match queues and exchanges, e.g. "^ha\..+". Required property. + // +kubebuilder:validation:Required + Pattern string `json:"pattern"` + // What this policy applies to: 'queues', 'exchanges', or 'all'. + // Default to 'all'. + // +kubebuilder:validation:Enum=queues;exchanges;all + // +kubebuilder:default:=all + ApplyTo string `json:"applyTo,omitempty"` + // Default to '0' + // +kubebuilder:default:=0 + Priority int `json:"priority,omitempty"` + // Policy definition. Required property. + // +kubebuilder:validation:Type=object + // +kubebuilder:pruning:PreserveUnknownFields + // +kubebuilder:validation:Required + Definition *runtime.RawExtension `json:"definition"` + // Reference to the RabbitmqCluster that the exchange will be created in. Required property. + // +kubebuilder:validation:Required + RabbitmqClusterReference RabbitmqClusterReference `json:"rabbitmqClusterReference"` +} + +// PolicyStatus defines the observed state of Policy +type PolicyStatus struct { +} + +// +kubebuilder:object:root=true + +// Policy is the Schema for the policies API +type Policy struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec PolicySpec `json:"spec,omitempty"` + Status PolicyStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// PolicyList contains a list of Policy +type PolicyList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Policy `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Policy{}, &PolicyList{}) +} diff --git a/api/v1alpha1/policy_types_test.go b/api/v1alpha1/policy_types_test.go new file mode 100644 index 00000000..37e748ec --- /dev/null +++ b/api/v1alpha1/policy_types_test.go @@ -0,0 +1,121 @@ +package v1alpha1 + +import ( + "context" + "k8s.io/apimachinery/pkg/runtime" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +var _ = Describe("Policy", func() { + var ( + namespace = "default" + ctx = context.Background() + ) + + It("creates a policy with minimal configurations", func() { + policy := Policy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-policy", + Namespace: namespace, + }, + Spec: PolicySpec{ + Name: "test-policy", + Pattern: "a-queue-name", + Definition: &runtime.RawExtension{ + Raw: []byte(`{"key":"value"}`), + }, + RabbitmqClusterReference: RabbitmqClusterReference{ + Name: "some-cluster", + Namespace: namespace, + }, + }, + } + Expect(k8sClient.Create(ctx, &policy)).To(Succeed()) + fetched := &Policy{} + Expect(k8sClient.Get(ctx, types.NamespacedName{ + Name: policy.Name, + Namespace: policy.Namespace, + }, fetched)).To(Succeed()) + Expect(fetched.Spec.RabbitmqClusterReference).To(Equal(RabbitmqClusterReference{ + Name: "some-cluster", + Namespace: namespace, + })) + Expect(fetched.Spec.Name).To(Equal("test-policy")) + Expect(fetched.Spec.Vhost).To(Equal("/")) + Expect(fetched.Spec.Pattern).To(Equal("a-queue-name")) + Expect(fetched.Spec.ApplyTo).To(Equal("all")) + Expect(fetched.Spec.Priority).To(Equal(0)) + Expect(fetched.Spec.Definition.Raw).To(Equal([]byte(`{"key":"value"}`))) + }) + + It("creates policy with configurations", func() { + policy := Policy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "random-policy", + Namespace: namespace, + }, + Spec: PolicySpec{ + Name: "test-policy", + Vhost: "/hello", + Pattern: "*.", + ApplyTo: "exchanges", + Priority: 100, + Definition: &runtime.RawExtension{ + Raw: []byte(`{"key":"value"}`), + }, + RabbitmqClusterReference: RabbitmqClusterReference{ + Name: "random-cluster", + Namespace: namespace, + }, + }, + } + Expect(k8sClient.Create(ctx, &policy)).To(Succeed()) + fetched := &Policy{} + Expect(k8sClient.Get(ctx, types.NamespacedName{ + Name: policy.Name, + Namespace: policy.Namespace, + }, fetched)).To(Succeed()) + + Expect(fetched.Spec.Name).To(Equal("test-policy")) + Expect(fetched.Spec.Vhost).To(Equal("/hello")) + Expect(fetched.Spec.Pattern).To(Equal("*.")) + Expect(fetched.Spec.ApplyTo).To(Equal("exchanges")) + Expect(fetched.Spec.Priority).To(Equal(100)) + Expect(fetched.Spec.RabbitmqClusterReference).To(Equal( + RabbitmqClusterReference{ + Name: "random-cluster", + Namespace: namespace, + })) + Expect(fetched.Spec.Definition.Raw).To(Equal([]byte(`{"key":"value"}`))) + }) + + When("creating a policy with an invalid 'ApplyTo' value", func() { + It("fails with validation errors", func() { + policy := Policy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "invalid", + Namespace: namespace, + }, + Spec: PolicySpec{ + Name: "test-policy", + Pattern: "a-queue-name", + Definition: &runtime.RawExtension{ + Raw: []byte(`{"key":"value"}`), + }, + ApplyTo: "yo-yo", + RabbitmqClusterReference: RabbitmqClusterReference{ + Name: "some-cluster", + Namespace: namespace, + }, + }, + } + Expect(k8sClient.Create(ctx, &policy)).To(HaveOccurred()) + Expect(k8sClient.Create(ctx, &policy)).To(MatchError(`Policy.rabbitmq.com "invalid" is invalid: spec.applyTo: Unsupported value: "yo-yo": supported values: "queues", "exchanges", "all"`)) + }) + }) + +}) diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 6fdd62e9..fabda23a 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -208,6 +208,101 @@ func (in *ExchangeStatus) DeepCopy() *ExchangeStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Policy) DeepCopyInto(out *Policy) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Policy. +func (in *Policy) DeepCopy() *Policy { + if in == nil { + return nil + } + out := new(Policy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Policy) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PolicyList) DeepCopyInto(out *PolicyList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Policy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyList. +func (in *PolicyList) DeepCopy() *PolicyList { + if in == nil { + return nil + } + out := new(PolicyList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PolicyList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PolicySpec) DeepCopyInto(out *PolicySpec) { + *out = *in + if in.Definition != nil { + in, out := &in.Definition, &out.Definition + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + out.RabbitmqClusterReference = in.RabbitmqClusterReference +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicySpec. +func (in *PolicySpec) DeepCopy() *PolicySpec { + if in == nil { + return nil + } + out := new(PolicySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PolicyStatus) DeepCopyInto(out *PolicyStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyStatus. +func (in *PolicyStatus) DeepCopy() *PolicyStatus { + if in == nil { + return nil + } + out := new(PolicyStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Queue) DeepCopyInto(out *Queue) { *out = *in diff --git a/config/crd/bases/rabbitmq.com_bindings.yaml b/config/crd/bases/rabbitmq.com_bindings.yaml index e92bd380..9ff04150 100644 --- a/config/crd/bases/rabbitmq.com_bindings.yaml +++ b/config/crd/bases/rabbitmq.com_bindings.yaml @@ -22,10 +22,14 @@ spec: description: Binding is the Schema for the bindings API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object @@ -43,7 +47,8 @@ spec: - queue type: string rabbitmqClusterReference: - description: Reference to the RabbitmqCluster that the exchange will be created in Required property + description: Reference to the RabbitmqCluster that the exchange will + be created in Required property properties: name: type: string diff --git a/config/crd/bases/rabbitmq.com_exchanges.yaml b/config/crd/bases/rabbitmq.com_exchanges.yaml index bfdaabcb..107c185e 100644 --- a/config/crd/bases/rabbitmq.com_exchanges.yaml +++ b/config/crd/bases/rabbitmq.com_exchanges.yaml @@ -22,10 +22,14 @@ spec: description: Exchange is the Schema for the exchanges API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object @@ -42,7 +46,8 @@ spec: name: type: string rabbitmqClusterReference: - description: Reference to the RabbitmqCluster that the exchange will be created in Required property + description: Reference to the RabbitmqCluster that the exchange will + be created in Required property properties: name: type: string diff --git a/config/crd/bases/rabbitmq.com_policies.yaml b/config/crd/bases/rabbitmq.com_policies.yaml new file mode 100644 index 00000000..02c3fb9c --- /dev/null +++ b/config/crd/bases/rabbitmq.com_policies.yaml @@ -0,0 +1,95 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.5.0 + creationTimestamp: null + name: policies.rabbitmq.com +spec: + group: rabbitmq.com + names: + kind: Policy + listKind: PolicyList + plural: policies + singular: policy + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: Policy is the Schema for the policies API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: PolicySpec defines the desired state of Policy + properties: + applyTo: + default: all + description: 'What this policy applies to: ''queues'', ''exchanges'', + or ''all''. Default to ''all''.' + enum: + - queues + - exchanges + - all + type: string + definition: + description: Policy definition. Required property. + type: object + x-kubernetes-preserve-unknown-fields: true + name: + type: string + pattern: + description: Regular expression pattern used to match queues and exchanges, + e.g. "^ha\..+". Required property. + type: string + priority: + default: 0 + description: Default to '0' + type: integer + rabbitmqClusterReference: + description: Reference to the RabbitmqCluster that the exchange will + be created in. Required property. + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + vhost: + default: / + description: Default to vhost '/' + type: string + required: + - definition + - name + - pattern + - rabbitmqClusterReference + type: object + status: + description: PolicyStatus defines the observed state of Policy + type: object + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/config/crd/bases/rabbitmq.com_queues.yaml b/config/crd/bases/rabbitmq.com_queues.yaml index 35c3f663..83a2bd3b 100644 --- a/config/crd/bases/rabbitmq.com_queues.yaml +++ b/config/crd/bases/rabbitmq.com_queues.yaml @@ -22,10 +22,14 @@ spec: description: Queue is the Schema for the queues API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object @@ -33,11 +37,13 @@ spec: description: QueueSpec defines the desired state of Queue properties: arguments: - description: 'Queue arguments in the format of KEY: VALUE. e.g. x-delivery-limit: 10000' + description: 'Queue arguments in the format of KEY: VALUE. e.g. x-delivery-limit: + 10000' type: object x-kubernetes-preserve-unknown-fields: true autoDelete: - description: when set to true, queues that has at least one consumer before, are deleted after last consumer unsubscribes + description: when set to true, queues that has at least one consumer + before, are deleted after last consumer unsubscribes type: boolean durable: description: When set to false queues does not survive server restart @@ -46,7 +52,8 @@ spec: description: Name of the queue; required property type: string rabbitmqClusterReference: - description: Reference to the RabbitmqCluster that the queue will be created in Required property + description: Reference to the RabbitmqCluster that the queue will + be created in Required property properties: name: type: string diff --git a/config/crd/bases/rabbitmq.com_users.yaml b/config/crd/bases/rabbitmq.com_users.yaml index 8005f2ff..7504ee2f 100644 --- a/config/crd/bases/rabbitmq.com_users.yaml +++ b/config/crd/bases/rabbitmq.com_users.yaml @@ -22,10 +22,14 @@ spec: description: User is the Schema for the users API. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object @@ -36,7 +40,8 @@ spec: description: Username of the user to create on a RabbitmqCluster. type: string rabbitmqClusterReference: - description: Reference to the RabbitmqCluster that the user will be created for. This cluster must exist for the User object to be created. + description: Reference to the RabbitmqCluster that the user will be + created for. This cluster must exist for the User object to be created. properties: name: type: string @@ -47,9 +52,15 @@ spec: - namespace type: object tags: - description: List of permissions tags to associate with the user. This determines the level of access to the RabbitMQ management UI granted to the user. Omitting this field will lead to a user than can still connect to the cluster through messaging protocols, but cannot perform any management actions. For more information, see https://www.rabbitmq.com/management.html#permissions. + description: List of permissions tags to associate with the user. + This determines the level of access to the RabbitMQ management UI + granted to the user. Omitting this field will lead to a user than + can still connect to the cluster through messaging protocols, but + cannot perform any management actions. For more information, see + https://www.rabbitmq.com/management.html#permissions. items: - description: UserTag defines the level of access to the management UI allocated to the user. For more information, see https://www.rabbitmq.com/management.html#permissions. + description: UserTag defines the level of access to the management + UI allocated to the user. For more information, see https://www.rabbitmq.com/management.html#permissions. enum: - management - policymaker @@ -65,10 +76,12 @@ spec: description: Status exposes the observed state of the User object. properties: credentials: - description: Provides a reference to a Secret object containing the user credentials. + description: Provides a reference to a Secret object containing the + user credentials. properties: name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' type: string type: object type: object diff --git a/config/crd/bases/rabbitmq.com_vhosts.yaml b/config/crd/bases/rabbitmq.com_vhosts.yaml index f25e745b..e97bc7b9 100644 --- a/config/crd/bases/rabbitmq.com_vhosts.yaml +++ b/config/crd/bases/rabbitmq.com_vhosts.yaml @@ -22,10 +22,14 @@ spec: description: Vhost is the Schema for the vhosts API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object @@ -33,10 +37,11 @@ spec: description: VhostSpec defines the desired state of Vhost properties: name: - description: Name of vhost + description: Name of the vhost; see https://www.rabbitmq.com/vhosts.html. type: string rabbitmqClusterReference: - description: Reference to the RabbitmqCluster that the queue will be created in Required property + description: Reference to the RabbitmqCluster that the vhost will + be created in Required property properties: name: type: string diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 2e30c822..0ee39489 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -7,6 +7,7 @@ resources: - bases/rabbitmq.com_bindings.yaml - bases/rabbitmq.com_users.yaml - bases/rabbitmq.com_vhosts.yaml +- bases/rabbitmq.com_policies.yaml # +kubebuilder:scaffold:crdkustomizeresource configurations: diff --git a/config/crd/patches/cainjection_in_policies.yaml b/config/crd/patches/cainjection_in_policies.yaml new file mode 100644 index 00000000..b88ffb6f --- /dev/null +++ b/config/crd/patches/cainjection_in_policies.yaml @@ -0,0 +1,8 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +# CRD conversion requires k8s 1.13 or later. +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: policies.rabbitmq.com diff --git a/config/crd/patches/webhook_in_policies.yaml b/config/crd/patches/webhook_in_policies.yaml new file mode 100644 index 00000000..35cd2a57 --- /dev/null +++ b/config/crd/patches/webhook_in_policies.yaml @@ -0,0 +1,17 @@ +# The following patch enables conversion webhook for CRD +# CRD conversion requires k8s 1.13 or later. +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: policies.rabbitmq.com +spec: + conversion: + strategy: Webhook + webhookClientConfig: + # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, + # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) + caBundle: Cg== + service: + namespace: system + name: webhook-service + path: /convert diff --git a/config/rbac/policy_editor_role.yaml b/config/rbac/policy_editor_role.yaml new file mode 100644 index 00000000..35877f5f --- /dev/null +++ b/config/rbac/policy_editor_role.yaml @@ -0,0 +1,24 @@ +# permissions for end users to edit policies. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: policy-editor-role +rules: +- apiGroups: + - rabbitmq.com + resources: + - policies + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - rabbitmq.com + resources: + - policies/status + verbs: + - get diff --git a/config/rbac/policy_viewer_role.yaml b/config/rbac/policy_viewer_role.yaml new file mode 100644 index 00000000..357bd5c1 --- /dev/null +++ b/config/rbac/policy_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view policies. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: policy-viewer-role +rules: +- apiGroups: + - rabbitmq.com + resources: + - policies + verbs: + - get + - list + - watch +- apiGroups: + - rabbitmq.com + resources: + - policies/status + verbs: + - get diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index c6d5f572..64f83038 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -74,6 +74,26 @@ rules: - get - patch - update +- apiGroups: + - rabbitmq.com + resources: + - policies + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - rabbitmq.com + resources: + - policies/status + verbs: + - get + - patch + - update - apiGroups: - rabbitmq.com resources: diff --git a/config/samples/rabbitmq.com_v1alpha1_policy.yaml b/config/samples/rabbitmq.com_v1alpha1_policy.yaml new file mode 100644 index 00000000..a10b7358 --- /dev/null +++ b/config/samples/rabbitmq.com_v1alpha1_policy.yaml @@ -0,0 +1,7 @@ +apiVersion: rabbitmq.com/v1alpha1 +kind: Policy +metadata: + name: policy-sample +spec: + # Add fields here + foo: bar diff --git a/controllers/policy_controller.go b/controllers/policy_controller.go new file mode 100644 index 00000000..03e05688 --- /dev/null +++ b/controllers/policy_controller.go @@ -0,0 +1,146 @@ +/* +RabbitMQ Messaging Topology Kubernetes Operator +Copyright 2021 VMware, Inc. + +This product is licensed to you under the Mozilla Public License 2.0 license (the "License"). You may not use this product except in compliance with the Mozilla 2.0 License. + +This product may include a number of subcomponents with separate copyright notices and license terms. Your use of these subcomponents is subject to the terms and conditions of the subcomponent's license, as noted in the LICENSE file. +*/ + +package controllers + +import ( + "context" + "encoding/json" + "errors" + rabbithole "github.com/michaelklishin/rabbit-hole/v2" + "github.com/rabbitmq/messaging-topology-operator/internal" + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + topologyv1alpha1 "github.com/rabbitmq/messaging-topology-operator/api/v1alpha1" +) + +const policyFinalizer = "deletion.finalizers.policies.rabbitmq.com" + +// PolicyReconciler reconciles a Policy object +type PolicyReconciler struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme + Recorder record.EventRecorder +} + +// +kubebuilder:rbac:groups=rabbitmq.com,resources=policies,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=rabbitmq.com,resources=policies/status,verbs=get;update;patch + +func (r *PolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + logger := ctrl.LoggerFrom(ctx) + + policy := &topologyv1alpha1.Policy{} + + if err := r.Get(ctx, req.NamespacedName, policy); err != nil { + return reconcile.Result{}, client.IgnoreNotFound(err) + } + + rabbitClient, err := rabbitholeClient(ctx, r.Client, policy.Spec.RabbitmqClusterReference) + if err != nil { + logger.Error(err, "Failed to generate http rabbit client") + } + + if !policy.ObjectMeta.DeletionTimestamp.IsZero() { + logger.Info("Deleting") + return ctrl.Result{}, r.deletePolicy(ctx, rabbitClient, policy) + } + + if err := r.addFinalizerIfNeeded(ctx, policy); err != nil { + return ctrl.Result{}, err + } + + spec, err := json.Marshal(policy.Spec) + if err != nil { + logger.Error(err, "Failed to marshal policy spec") + } + + logger.Info("Start reconciling", + "spec", string(spec)) + + if err := r.putPolicy(ctx, rabbitClient, policy); err != nil { + return ctrl.Result{}, err + } + + logger.Info("Finished reconciling") + + return ctrl.Result{}, nil +} + +// creates or updates a given policy using rabbithole client.PutPolicy +func (r *PolicyReconciler) putPolicy(ctx context.Context, client *rabbithole.Client, policy *topologyv1alpha1.Policy) error { + logger := ctrl.LoggerFrom(ctx) + + generatePolicy, err := internal.GeneratePolicy(policy) + if err != nil { + msg := "failed to generate Policy" + r.Recorder.Event(policy, corev1.EventTypeWarning, "FailedCreateOrUpdate", msg) + logger.Error(err, msg) + return err + } + + if err = validateResponse(client.PutPolicy(policy.Spec.Vhost, policy.Spec.Name, *generatePolicy)); err != nil { + msg := "failed to create Policy" + r.Recorder.Event(policy, corev1.EventTypeWarning, "FailedCreateOrUpdate", msg) + logger.Error(err, msg, "policy", policy.Spec.Name) + return err + } + logger.Info("Successfully created policy", "policy", policy.Spec.Name) + r.Recorder.Event(policy, corev1.EventTypeNormal, "SuccessfulCreateOrUpdate", "Successfully created/updated policy") + return nil +} + +func (r *PolicyReconciler) addFinalizerIfNeeded(ctx context.Context, policy *topologyv1alpha1.Policy) error { + if policy.ObjectMeta.DeletionTimestamp.IsZero() && !controllerutil.ContainsFinalizer(policy, policyFinalizer) { + controllerutil.AddFinalizer(policy, policyFinalizer) + if err := r.Client.Update(ctx, policy); err != nil { + return err + } + } + return nil +} + +// deletes policy from rabbitmq server +// if server responds with '404' Not Found, it logs and does not requeue on error +func (r *PolicyReconciler) deletePolicy(ctx context.Context, client *rabbithole.Client, policy *topologyv1alpha1.Policy) error { + logger := ctrl.LoggerFrom(ctx) + + err := validateResponseForDeletion(client.DeletePolicy(policy.Spec.Vhost, policy.Spec.Name)) + if errors.Is(err, NotFound) { + logger.Info("cannot find policy in rabbitmq server; already deleted", "policy", policy.Spec.Name) + } else if err != nil { + msg := "failed to delete policy" + r.Recorder.Event(policy, corev1.EventTypeWarning, "FailedDelete", msg) + logger.Error(err, msg, "policy", policy.Spec.Name) + return err + } + return r.removeFinalizer(ctx, policy) +} + +func (r *PolicyReconciler) removeFinalizer(ctx context.Context, policy *topologyv1alpha1.Policy) error { + controllerutil.RemoveFinalizer(policy, policyFinalizer) + if err := r.Client.Update(ctx, policy); err != nil { + return err + } + return nil +} + +func (r *PolicyReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&topologyv1alpha1.Policy{}). + Complete(r) +} diff --git a/docs/examples/policies/policy.yaml b/docs/examples/policies/policy.yaml new file mode 100644 index 00000000..2bf6ebd8 --- /dev/null +++ b/docs/examples/policies/policy.yaml @@ -0,0 +1,14 @@ +apiVersion: rabbitmq.com/v1alpha1 +kind: Policy +metadata: + name: policy-example +spec: + name: transient # name of the policy + vhost: "/a-vhost" # default to '/' if not provided + pattern: "^amq." # regex used to match queues and exchanges + applyTo: "queues" # set to 'queues', 'exchanges', or 'all' + definition: # policy definition + expires: 1800000 + rabbitmqClusterReference: + name: test + namespace: rabbitmq-system diff --git a/internal/policy.go b/internal/policy.go new file mode 100644 index 00000000..7c2e339e --- /dev/null +++ b/internal/policy.go @@ -0,0 +1,35 @@ +/* +RabbitMQ Messaging Topology Kubernetes Operator +Copyright 2021 VMware, Inc. + +This product is licensed to you under the Mozilla Public License 2.0 license (the "License"). You may not use this product except in compliance with the Mozilla 2.0 License. + +This product may include a number of subcomponents with separate copyright notices and license terms. Your use of these subcomponents is subject to the terms and conditions of the subcomponent's license, as noted in the LICENSE file. +*/ + +package internal + +import ( + "encoding/json" + "fmt" + rabbithole "github.com/michaelklishin/rabbit-hole/v2" + topologyv1alpha1 "github.com/rabbitmq/messaging-topology-operator/api/v1alpha1" +) + +func GeneratePolicy(p *topologyv1alpha1.Policy) (*rabbithole.Policy, error) { + definition := make(map[string]interface{}) + if p.Spec.Definition != nil { + if err := json.Unmarshal(p.Spec.Definition.Raw, &definition); err != nil { + return nil, fmt.Errorf("failed to unmarshall policy definition: %v", err) + } + } + + return &rabbithole.Policy{ + Vhost: p.Spec.Vhost, + Pattern: p.Spec.Pattern, + ApplyTo: p.Spec.ApplyTo, + Name: p.Spec.Name, + Priority: p.Spec.Priority, + Definition: definition, + }, nil +} diff --git a/internal/policy_test.go b/internal/policy_test.go new file mode 100644 index 00000000..1e21996c --- /dev/null +++ b/internal/policy_test.go @@ -0,0 +1,70 @@ +package internal_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + topologyv1alpha1 "github.com/rabbitmq/messaging-topology-operator/api/v1alpha1" + . "github.com/rabbitmq/messaging-topology-operator/internal" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +var _ = Describe("GeneratePolicy", func() { + var p *topologyv1alpha1.Policy + + BeforeEach(func() { + p = &topologyv1alpha1.Policy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "new-policy", + }, + Spec: topologyv1alpha1.PolicySpec{ + Name: "new-p", + Vhost: "/new-vhost", + ApplyTo: "exchanges", + Pattern: "exchange-name", + Priority: 5, + }, + } + }) + + It("sets policy name according to policySpec", func() { + generated, err := GeneratePolicy(p) + Expect(err).NotTo(HaveOccurred()) + Expect(generated.Name).To(Equal("new-p")) + }) + + It("sets policy vhost according to policySpec", func() { + generated, err := GeneratePolicy(p) + Expect(err).NotTo(HaveOccurred()) + Expect(generated.Vhost).To(Equal("/new-vhost")) + }) + + It("sets 'ApplyTo' according to policySpec", func() { + generated, err := GeneratePolicy(p) + Expect(err).NotTo(HaveOccurred()) + Expect(generated.ApplyTo).To(Equal("exchanges")) + }) + + It("sets 'priority' according to policySpec", func() { + generated, err := GeneratePolicy(p) + Expect(err).NotTo(HaveOccurred()) + Expect(generated.Priority).To(Equal(5)) + }) + + It("sets 'pattern' according to policySpec", func() { + generated, err := GeneratePolicy(p) + Expect(err).NotTo(HaveOccurred()) + Expect(generated.Pattern).To(Equal("exchange-name")) + }) + + When("policy definition are provided", func() { + It("sets definition correctly", func() { + p.Spec.Definition = &runtime.RawExtension{ + Raw: []byte(`{"key": "value"}`)} + generated, err := GeneratePolicy(p) + Expect(err).NotTo(HaveOccurred()) + Expect(generated.Definition).Should(HaveLen(1)) + Expect(generated.Definition).Should(HaveKeyWithValue("key", "value")) + }) + }) +}) diff --git a/main.go b/main.go index 12b08827..94379a36 100644 --- a/main.go +++ b/main.go @@ -31,6 +31,7 @@ const queueControllerName = "queue-controller" const exchangeControllerName = "exchange-controller" const bindingControllerName = "binding-controller" const userControllerName = "user-controller" +const policyControllerName = "policy-controller" var ( scheme = runtime.NewScheme() @@ -100,14 +101,23 @@ func main() { os.Exit(1) } if err = (&controllers.VhostReconciler{ - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("Vhost"), - Scheme: mgr.GetScheme(), + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("Vhost"), + Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorderFor(vhostControllerName), }).SetupWithManager(mgr); err != nil { log.Error(err, "unable to create controller", "controller", vhostControllerName) os.Exit(1) } + if err = (&controllers.PolicyReconciler{ + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("Policy"), + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorderFor(policyControllerName), + }).SetupWithManager(mgr); err != nil { + log.Error(err, "unable to create controller", "controller", policyControllerName) + os.Exit(1) + } // +kubebuilder:scaffold:builder log.Info("starting manager") diff --git a/system_tests/policy_system_tests.go b/system_tests/policy_system_tests.go new file mode 100644 index 00000000..a1149901 --- /dev/null +++ b/system_tests/policy_system_tests.go @@ -0,0 +1,93 @@ +package system_tests + +import ( + "context" + rabbithole "github.com/michaelklishin/rabbit-hole/v2" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gstruct" + + topologyv1alpha1 "github.com/rabbitmq/messaging-topology-operator/api/v1alpha1" +) + +var _ = Describe("Policy", func() { + var ( + namespace = MustHaveEnv("NAMESPACE") + ctx = context.Background() + policy *topologyv1alpha1.Policy + ) + + BeforeEach(func() { + policy = &topologyv1alpha1.Policy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "policy-test", + Namespace: namespace, + }, + Spec: topologyv1alpha1.PolicySpec{ + RabbitmqClusterReference: topologyv1alpha1.RabbitmqClusterReference{ + Name: rmq.Name, + Namespace: rmq.Namespace, + }, + Name: "policy-test", + Pattern: "test-queue", + ApplyTo: "queues", + Definition: &runtime.RawExtension{ + Raw: []byte(`{"ha-mode":"all"}`), + }, + }, + } + }) + + It("creates, updates and deletes a policy successfully", func() { + By("creating policy") + Expect(k8sClient.Create(ctx, policy, &client.CreateOptions{})).To(Succeed()) + var fetchedPolicy *rabbithole.Policy + Eventually(func() error { + var err error + fetchedPolicy, err = rabbitClient.GetPolicy(policy.Spec.Vhost, policy.Name) + return err + }, 10, 2).Should(BeNil()) + + Expect(*fetchedPolicy).To(MatchFields(IgnoreExtras, Fields{ + "Name": Equal(policy.Spec.Name), + "Vhost": Equal(policy.Spec.Vhost), + "Pattern": Equal("test-queue"), + "ApplyTo": Equal("queues"), + "Priority": Equal(0), + })) + + Expect(fetchedPolicy.Definition).To(HaveKeyWithValue("ha-mode", "all")) + + By("updating policy") + Expect(k8sClient.Get(ctx, types.NamespacedName{Name: policy.Name, Namespace: policy.Namespace}, policy)).To(Succeed()) + policy.Spec.Definition = &runtime.RawExtension{ + Raw: []byte(`{"ha-mode":"exactly", +"ha-params": 2 +}`)} + Expect(k8sClient.Update(ctx, policy, &client.UpdateOptions{})).To(Succeed()) + + Eventually(func() rabbithole.PolicyDefinition { + var err error + fetchedPolicy, err = rabbitClient.GetPolicy(policy.Spec.Vhost, policy.Name) + Expect(err).NotTo(HaveOccurred()) + return fetchedPolicy.Definition + }, 10, 2).Should(HaveLen(2)) + + Expect(fetchedPolicy.Definition).To(HaveKeyWithValue("ha-mode", "exactly")) + Expect(fetchedPolicy.Definition).To(HaveKeyWithValue("ha-params", float64(2))) + + By("deleting policy") + Expect(k8sClient.Delete(ctx, policy)).To(Succeed()) + var err error + Eventually(func() error { + _, err = rabbitClient.GetPolicy(policy.Spec.Vhost, policy.Name) + return err + }, 10).Should(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Object Not Found")) + }) +}) From 5a5abd19faa666ca1672b23cc5fef61cb5d1d980 Mon Sep 17 00:00:00 2001 From: Chunyi Lyu Date: Tue, 9 Mar 2021 16:40:19 +0000 Subject: [PATCH 2/3] Correct crd descriptions --- api/v1alpha1/binding_types.go | 4 ++-- api/v1alpha1/exchange_types.go | 4 ++-- api/v1alpha1/policy_types.go | 6 ++++-- api/v1alpha1/queue_types.go | 4 ++-- api/v1alpha1/vhost_types.go | 4 ++-- config/crd/bases/rabbitmq.com_bindings.yaml | 2 +- config/crd/bases/rabbitmq.com_exchanges.yaml | 2 +- config/crd/bases/rabbitmq.com_policies.yaml | 2 +- config/crd/bases/rabbitmq.com_queues.yaml | 2 +- config/crd/bases/rabbitmq.com_vhosts.yaml | 2 +- 10 files changed, 17 insertions(+), 15 deletions(-) diff --git a/api/v1alpha1/binding_types.go b/api/v1alpha1/binding_types.go index fe99201e..1f6c5ef1 100644 --- a/api/v1alpha1/binding_types.go +++ b/api/v1alpha1/binding_types.go @@ -31,8 +31,8 @@ type BindingSpec struct { // +kubebuilder:validation:Type=object // +kubebuilder:pruning:PreserveUnknownFields Arguments *runtime.RawExtension `json:"arguments,omitempty"` - // Reference to the RabbitmqCluster that the exchange will be created in - // Required property + // Reference to the RabbitmqCluster that the exchange will be created in. + // Required property. // +kubebuilder:validation:Required RabbitmqClusterReference RabbitmqClusterReference `json:"rabbitmqClusterReference"` } diff --git a/api/v1alpha1/exchange_types.go b/api/v1alpha1/exchange_types.go index 4620f32d..6a77c053 100644 --- a/api/v1alpha1/exchange_types.go +++ b/api/v1alpha1/exchange_types.go @@ -29,8 +29,8 @@ type ExchangeSpec struct { // +kubebuilder:validation:Type=object // +kubebuilder:pruning:PreserveUnknownFields Arguments *runtime.RawExtension `json:"arguments,omitempty"` - // Reference to the RabbitmqCluster that the exchange will be created in - // Required property + // Reference to the RabbitmqCluster that the exchange will be created in. + // Required property. // +kubebuilder:validation:Required RabbitmqClusterReference RabbitmqClusterReference `json:"rabbitmqClusterReference"` } diff --git a/api/v1alpha1/policy_types.go b/api/v1alpha1/policy_types.go index 0b33fd63..cf684f8d 100644 --- a/api/v1alpha1/policy_types.go +++ b/api/v1alpha1/policy_types.go @@ -12,7 +12,8 @@ type PolicySpec struct { // Default to vhost '/' // +kubebuilder:default:=/ Vhost string `json:"vhost,omitempty"` - // Regular expression pattern used to match queues and exchanges, e.g. "^ha\..+". Required property. + // Regular expression pattern used to match queues and exchanges, e.g. "^amq.". + // Required property. // +kubebuilder:validation:Required Pattern string `json:"pattern"` // What this policy applies to: 'queues', 'exchanges', or 'all'. @@ -28,7 +29,8 @@ type PolicySpec struct { // +kubebuilder:pruning:PreserveUnknownFields // +kubebuilder:validation:Required Definition *runtime.RawExtension `json:"definition"` - // Reference to the RabbitmqCluster that the exchange will be created in. Required property. + // Reference to the RabbitmqCluster that the exchange will be created in. + // Required property. // +kubebuilder:validation:Required RabbitmqClusterReference RabbitmqClusterReference `json:"rabbitmqClusterReference"` } diff --git a/api/v1alpha1/queue_types.go b/api/v1alpha1/queue_types.go index d1b4fca2..e6cc1afe 100644 --- a/api/v1alpha1/queue_types.go +++ b/api/v1alpha1/queue_types.go @@ -35,8 +35,8 @@ type QueueSpec struct { // +kubebuilder:validation:Type=object // +kubebuilder:pruning:PreserveUnknownFields Arguments *runtime.RawExtension `json:"arguments,omitempty"` - // Reference to the RabbitmqCluster that the queue will be created in - // Required property + // Reference to the RabbitmqCluster that the queue will be created in. + // Required property. // +kubebuilder:validation:Required RabbitmqClusterReference RabbitmqClusterReference `json:"rabbitmqClusterReference"` } diff --git a/api/v1alpha1/vhost_types.go b/api/v1alpha1/vhost_types.go index edd3f9de..6e65f198 100644 --- a/api/v1alpha1/vhost_types.go +++ b/api/v1alpha1/vhost_types.go @@ -19,8 +19,8 @@ type VhostSpec struct { // +kubebuilder:validation:Required Name string `json:"name"` Tracing bool `json:"tracing,omitempty"` - // Reference to the RabbitmqCluster that the vhost will be created in - // Required property + // Reference to the RabbitmqCluster that the vhost will be created in. + // Required property. // +kubebuilder:validation:Required RabbitmqClusterReference RabbitmqClusterReference `json:"rabbitmqClusterReference"` } diff --git a/config/crd/bases/rabbitmq.com_bindings.yaml b/config/crd/bases/rabbitmq.com_bindings.yaml index 9ff04150..82fac062 100644 --- a/config/crd/bases/rabbitmq.com_bindings.yaml +++ b/config/crd/bases/rabbitmq.com_bindings.yaml @@ -48,7 +48,7 @@ spec: type: string rabbitmqClusterReference: description: Reference to the RabbitmqCluster that the exchange will - be created in Required property + be created in. Required property. properties: name: type: string diff --git a/config/crd/bases/rabbitmq.com_exchanges.yaml b/config/crd/bases/rabbitmq.com_exchanges.yaml index 107c185e..9335d87b 100644 --- a/config/crd/bases/rabbitmq.com_exchanges.yaml +++ b/config/crd/bases/rabbitmq.com_exchanges.yaml @@ -47,7 +47,7 @@ spec: type: string rabbitmqClusterReference: description: Reference to the RabbitmqCluster that the exchange will - be created in Required property + be created in. Required property. properties: name: type: string diff --git a/config/crd/bases/rabbitmq.com_policies.yaml b/config/crd/bases/rabbitmq.com_policies.yaml index 02c3fb9c..6b4c98e0 100644 --- a/config/crd/bases/rabbitmq.com_policies.yaml +++ b/config/crd/bases/rabbitmq.com_policies.yaml @@ -53,7 +53,7 @@ spec: type: string pattern: description: Regular expression pattern used to match queues and exchanges, - e.g. "^ha\..+". Required property. + e.g. "^amq.". Required property. type: string priority: default: 0 diff --git a/config/crd/bases/rabbitmq.com_queues.yaml b/config/crd/bases/rabbitmq.com_queues.yaml index 83a2bd3b..15fb0f56 100644 --- a/config/crd/bases/rabbitmq.com_queues.yaml +++ b/config/crd/bases/rabbitmq.com_queues.yaml @@ -53,7 +53,7 @@ spec: type: string rabbitmqClusterReference: description: Reference to the RabbitmqCluster that the queue will - be created in Required property + be created in. Required property. properties: name: type: string diff --git a/config/crd/bases/rabbitmq.com_vhosts.yaml b/config/crd/bases/rabbitmq.com_vhosts.yaml index e97bc7b9..d74ec586 100644 --- a/config/crd/bases/rabbitmq.com_vhosts.yaml +++ b/config/crd/bases/rabbitmq.com_vhosts.yaml @@ -41,7 +41,7 @@ spec: type: string rabbitmqClusterReference: description: Reference to the RabbitmqCluster that the vhost will - be created in Required property + be created in. Required property. properties: name: type: string From 8e14115fbf746f974f28bb21ae09bd0590a2367a Mon Sep 17 00:00:00 2001 From: Chunyi Lyu Date: Tue, 9 Mar 2021 17:12:23 +0000 Subject: [PATCH 3/3] Correct minor issues with crds description --- api/v1alpha1/binding_types.go | 2 +- api/v1alpha1/policy_types.go | 4 +++- config/crd/bases/rabbitmq.com_bindings.yaml | 2 +- config/crd/bases/rabbitmq.com_policies.yaml | 6 +++-- docs/examples/policies/policy.yaml | 1 + internal/policy.go | 6 ++--- internal/policy_test.go | 25 +++++++++------------ 7 files changed, 23 insertions(+), 23 deletions(-) diff --git a/api/v1alpha1/binding_types.go b/api/v1alpha1/binding_types.go index 1f6c5ef1..fa6b0fda 100644 --- a/api/v1alpha1/binding_types.go +++ b/api/v1alpha1/binding_types.go @@ -31,7 +31,7 @@ type BindingSpec struct { // +kubebuilder:validation:Type=object // +kubebuilder:pruning:PreserveUnknownFields Arguments *runtime.RawExtension `json:"arguments,omitempty"` - // Reference to the RabbitmqCluster that the exchange will be created in. + // Reference to the RabbitmqCluster that the binding will be created in. // Required property. // +kubebuilder:validation:Required RabbitmqClusterReference RabbitmqClusterReference `json:"rabbitmqClusterReference"` diff --git a/api/v1alpha1/policy_types.go b/api/v1alpha1/policy_types.go index cf684f8d..f179468e 100644 --- a/api/v1alpha1/policy_types.go +++ b/api/v1alpha1/policy_types.go @@ -6,6 +6,7 @@ import ( ) // PolicySpec defines the desired state of Policy +// https://www.rabbitmq.com/parameters.html#policies type PolicySpec struct { // +kubebuilder:validation:Required Name string `json:"name"` @@ -21,7 +22,8 @@ type PolicySpec struct { // +kubebuilder:validation:Enum=queues;exchanges;all // +kubebuilder:default:=all ApplyTo string `json:"applyTo,omitempty"` - // Default to '0' + // Default to '0'. + // In the event that more than one policy can match a given exchange or queue, the policy with the greatest priority applies. // +kubebuilder:default:=0 Priority int `json:"priority,omitempty"` // Policy definition. Required property. diff --git a/config/crd/bases/rabbitmq.com_bindings.yaml b/config/crd/bases/rabbitmq.com_bindings.yaml index 82fac062..9c665719 100644 --- a/config/crd/bases/rabbitmq.com_bindings.yaml +++ b/config/crd/bases/rabbitmq.com_bindings.yaml @@ -47,7 +47,7 @@ spec: - queue type: string rabbitmqClusterReference: - description: Reference to the RabbitmqCluster that the exchange will + description: Reference to the RabbitmqCluster that the binding will be created in. Required property. properties: name: diff --git a/config/crd/bases/rabbitmq.com_policies.yaml b/config/crd/bases/rabbitmq.com_policies.yaml index 6b4c98e0..0ea9e879 100644 --- a/config/crd/bases/rabbitmq.com_policies.yaml +++ b/config/crd/bases/rabbitmq.com_policies.yaml @@ -34,7 +34,7 @@ spec: metadata: type: object spec: - description: PolicySpec defines the desired state of Policy + description: PolicySpec defines the desired state of Policy https://www.rabbitmq.com/parameters.html#policies properties: applyTo: default: all @@ -57,7 +57,9 @@ spec: type: string priority: default: 0 - description: Default to '0' + description: Default to '0'. In the event that more than one policy + can match a given exchange or queue, the policy with the greatest + priority applies. type: integer rabbitmqClusterReference: description: Reference to the RabbitmqCluster that the exchange will diff --git a/docs/examples/policies/policy.yaml b/docs/examples/policies/policy.yaml index 2bf6ebd8..d147644f 100644 --- a/docs/examples/policies/policy.yaml +++ b/docs/examples/policies/policy.yaml @@ -2,6 +2,7 @@ apiVersion: rabbitmq.com/v1alpha1 kind: Policy metadata: name: policy-example + namespace: rabbitmq-system spec: name: transient # name of the policy vhost: "/a-vhost" # default to '/' if not provided diff --git a/internal/policy.go b/internal/policy.go index 7c2e339e..75345b05 100644 --- a/internal/policy.go +++ b/internal/policy.go @@ -18,10 +18,8 @@ import ( func GeneratePolicy(p *topologyv1alpha1.Policy) (*rabbithole.Policy, error) { definition := make(map[string]interface{}) - if p.Spec.Definition != nil { - if err := json.Unmarshal(p.Spec.Definition.Raw, &definition); err != nil { - return nil, fmt.Errorf("failed to unmarshall policy definition: %v", err) - } + if err := json.Unmarshal(p.Spec.Definition.Raw, &definition); err != nil { + return nil, fmt.Errorf("failed to unmarshall policy definition: %v", err) } return &rabbithole.Policy{ diff --git a/internal/policy_test.go b/internal/policy_test.go index 1e21996c..9212b6b0 100644 --- a/internal/policy_test.go +++ b/internal/policy_test.go @@ -18,11 +18,12 @@ var _ = Describe("GeneratePolicy", func() { Name: "new-policy", }, Spec: topologyv1alpha1.PolicySpec{ - Name: "new-p", - Vhost: "/new-vhost", - ApplyTo: "exchanges", - Pattern: "exchange-name", - Priority: 5, + Name: "new-p", + Vhost: "/new-vhost", + ApplyTo: "exchanges", + Pattern: "exchange-name", + Priority: 5, + Definition: &runtime.RawExtension{Raw: []byte(`{"key":"value"}`)}, }, } }) @@ -57,14 +58,10 @@ var _ = Describe("GeneratePolicy", func() { Expect(generated.Pattern).To(Equal("exchange-name")) }) - When("policy definition are provided", func() { - It("sets definition correctly", func() { - p.Spec.Definition = &runtime.RawExtension{ - Raw: []byte(`{"key": "value"}`)} - generated, err := GeneratePolicy(p) - Expect(err).NotTo(HaveOccurred()) - Expect(generated.Definition).Should(HaveLen(1)) - Expect(generated.Definition).Should(HaveKeyWithValue("key", "value")) - }) + It("sets definition according to policySpec", func() { + generated, err := GeneratePolicy(p) + Expect(err).NotTo(HaveOccurred()) + Expect(generated.Definition).Should(HaveLen(1)) + Expect(generated.Definition).Should(HaveKeyWithValue("key", "value")) }) })