Skip to content

Commit 186d57c

Browse files
committed
Add topicpermissions.rabbitmq.com
- issue #191
1 parent 7d421ab commit 186d57c

21 files changed

+959
-16
lines changed

PROJECT

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,16 @@ resources:
105105
webhooks:
106106
validation: true
107107
webhookVersion: v1
108+
- api:
109+
crdVersion: v1
110+
namespaced: true
111+
controller: true
112+
domain: rabbitmq.com
113+
group: rabbitmq.com
114+
kind: TopicPermission
115+
path: github.com/rabbitmq/messaging-topology-operator/api/v1beta1
116+
version: v1beta1
117+
webhooks:
118+
validation: true
119+
webhookVersion: v1
108120
version: "3"

api/v1alpha1/groupversion_info.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ This product may include a number of subcomponents with separate copyright notic
88
*/
99

1010
// Package v1alpha1 contains API Schema definitions for the rabbitmq.com v1alpha1 API group
11-
//+kubebuilder:object:generate=true
12-
//+groupName=rabbitmq.com
11+
// +kubebuilder:object:generate=true
12+
// +groupName=rabbitmq.com
1313
package v1alpha1
1414

1515
import (
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package v1beta1
2+
3+
import (
4+
corev1 "k8s.io/api/core/v1"
5+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
6+
"k8s.io/apimachinery/pkg/runtime/schema"
7+
)
8+
9+
// TopicPermissionSpec defines the desired state of TopicPermission
10+
type TopicPermissionSpec struct {
11+
// Name of an existing user; must provide user or userReference, else create/update will fail; cannot be updated
12+
User string `json:"user,omitempty"`
13+
// Reference to an existing user.rabbitmq.com object; must provide user or userReference, else create/update will fail; cannot be updated
14+
UserReference *corev1.LocalObjectReference `json:"userReference,omitempty"`
15+
// Name of an existing vhost; required property; cannot be updated
16+
// +kubebuilder:validation:Required
17+
Vhost string `json:"vhost"`
18+
// Permissions to grant to the user permissions to a topic exchangerequired property.
19+
// +kubebuilder:validation:Required
20+
Permissions TopPermissionsConfig `json:"permissions"`
21+
// Reference to the RabbitmqCluster that both the provided user and vhost are.
22+
// Required property.
23+
// +kubebuilder:validation:Required
24+
RabbitmqClusterReference RabbitmqClusterReference `json:"rabbitmqClusterReference"`
25+
}
26+
27+
type TopPermissionsConfig struct {
28+
// +kubebuilder:validation:Optional
29+
Exchange string `json:"exchange,omitempty"`
30+
// +kubebuilder:validation:Optional
31+
Read string `json:"read,omitempty"`
32+
// +kubebuilder:validation:Optional
33+
Write string `json:"write,omitempty"`
34+
}
35+
36+
// TopicPermissionStatus defines the observed state of TopicPermission
37+
type TopicPermissionStatus struct {
38+
// observedGeneration is the most recent successful generation observed for this TopicPermission. It corresponds to the
39+
// TopicPermission's generation, which is updated on mutation by the API Server.
40+
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
41+
Conditions []Condition `json:"conditions,omitempty"`
42+
}
43+
44+
//+kubebuilder:object:root=true
45+
//+kubebuilder:subresource:status
46+
47+
// TopicPermission is the Schema for the topicpermissions API
48+
type TopicPermission struct {
49+
metav1.TypeMeta `json:",inline"`
50+
metav1.ObjectMeta `json:"metadata,omitempty"`
51+
52+
Spec TopicPermissionSpec `json:"spec,omitempty"`
53+
Status TopicPermissionStatus `json:"status,omitempty"`
54+
}
55+
56+
//+kubebuilder:object:root=true
57+
58+
// TopicPermissionList contains a list of TopicPermission
59+
type TopicPermissionList struct {
60+
metav1.TypeMeta `json:",inline"`
61+
metav1.ListMeta `json:"metadata,omitempty"`
62+
Items []TopicPermission `json:"items"`
63+
}
64+
65+
func (t *TopicPermission) GroupResource() schema.GroupResource {
66+
return schema.GroupResource{
67+
Group: t.GroupVersionKind().Group,
68+
Resource: t.GroupVersionKind().Kind,
69+
}
70+
}
71+
72+
func (t *TopicPermission) RabbitReference() RabbitmqClusterReference {
73+
return t.Spec.RabbitmqClusterReference
74+
}
75+
76+
func (t *TopicPermission) SetStatusConditions(c []Condition) {
77+
t.Status.Conditions = c
78+
}
79+
80+
func init() {
81+
SchemeBuilder.Register(&TopicPermission{}, &TopicPermissionList{})
82+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package v1beta1
2+
3+
import (
4+
"context"
5+
. "github.com/onsi/ginkgo/v2"
6+
. "github.com/onsi/gomega"
7+
corev1 "k8s.io/api/core/v1"
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
"k8s.io/apimachinery/pkg/types"
10+
)
11+
12+
var _ = Describe("TopicPermission", func() {
13+
var (
14+
namespace = "default"
15+
ctx = context.Background()
16+
)
17+
18+
It("creates a topic permission with no permission configured", func() {
19+
permission := TopicPermission{
20+
ObjectMeta: metav1.ObjectMeta{
21+
Name: "test-permission-0",
22+
Namespace: namespace,
23+
},
24+
Spec: TopicPermissionSpec{
25+
User: "test",
26+
Vhost: "/test",
27+
Permissions: TopPermissionsConfig{},
28+
RabbitmqClusterReference: RabbitmqClusterReference{
29+
Name: "some-cluster",
30+
},
31+
},
32+
}
33+
Expect(k8sClient.Create(ctx, &permission)).To(Succeed())
34+
fetchedTopicPermission := &TopicPermission{}
35+
Expect(k8sClient.Get(ctx, types.NamespacedName{
36+
Name: permission.Name,
37+
Namespace: permission.Namespace,
38+
}, fetchedTopicPermission)).To(Succeed())
39+
Expect(fetchedTopicPermission.Spec.User).To(Equal("test"))
40+
Expect(fetchedTopicPermission.Spec.Vhost).To(Equal("/test"))
41+
Expect(fetchedTopicPermission.Spec.RabbitmqClusterReference.Name).To(Equal("some-cluster"))
42+
43+
Expect(fetchedTopicPermission.Spec.Permissions.Exchange).To(Equal(""))
44+
Expect(fetchedTopicPermission.Spec.Permissions.Write).To(Equal(""))
45+
Expect(fetchedTopicPermission.Spec.Permissions.Read).To(Equal(""))
46+
})
47+
48+
It("creates a topic permission with permissions all configured", func() {
49+
permission := TopicPermission{
50+
ObjectMeta: metav1.ObjectMeta{
51+
Name: "test-permission-1",
52+
Namespace: namespace,
53+
},
54+
Spec: TopicPermissionSpec{
55+
User: "test",
56+
Vhost: "/test",
57+
Permissions: TopPermissionsConfig{
58+
Exchange: "some",
59+
Read: "^?",
60+
Write: ".*",
61+
},
62+
RabbitmqClusterReference: RabbitmqClusterReference{
63+
Name: "some-cluster",
64+
},
65+
},
66+
}
67+
Expect(k8sClient.Create(ctx, &permission)).To(Succeed())
68+
fetchedTopicPermission := &TopicPermission{}
69+
Expect(k8sClient.Get(ctx, types.NamespacedName{
70+
Name: permission.Name,
71+
Namespace: permission.Namespace,
72+
}, fetchedTopicPermission)).To(Succeed())
73+
Expect(fetchedTopicPermission.Spec.User).To(Equal("test"))
74+
Expect(fetchedTopicPermission.Spec.Vhost).To(Equal("/test"))
75+
Expect(fetchedTopicPermission.Spec.RabbitmqClusterReference.Name).To(Equal("some-cluster"))
76+
77+
Expect(fetchedTopicPermission.Spec.Permissions.Exchange).To(Equal("some"))
78+
Expect(fetchedTopicPermission.Spec.Permissions.Write).To(Equal(".*"))
79+
Expect(fetchedTopicPermission.Spec.Permissions.Read).To(Equal("^?"))
80+
})
81+
82+
It("creates a permission object with user reference provided", func() {
83+
permission := TopicPermission{
84+
ObjectMeta: metav1.ObjectMeta{
85+
Name: "user-ref-permission",
86+
Namespace: namespace,
87+
},
88+
Spec: TopicPermissionSpec{
89+
UserReference: &corev1.LocalObjectReference{
90+
Name: "a-created-user",
91+
},
92+
Vhost: "/test",
93+
Permissions: TopPermissionsConfig{},
94+
RabbitmqClusterReference: RabbitmqClusterReference{
95+
Name: "some-cluster",
96+
},
97+
},
98+
}
99+
Expect(k8sClient.Create(ctx, &permission)).To(Succeed())
100+
fetchedTopicPermission := &TopicPermission{}
101+
Expect(k8sClient.Get(ctx, types.NamespacedName{
102+
Name: permission.Name,
103+
Namespace: permission.Namespace,
104+
}, fetchedTopicPermission)).To(Succeed())
105+
Expect(fetchedTopicPermission.Spec.UserReference.Name).To(Equal("a-created-user"))
106+
Expect(fetchedTopicPermission.Spec.User).To(Equal(""))
107+
Expect(fetchedTopicPermission.Spec.Vhost).To(Equal("/test"))
108+
Expect(fetchedTopicPermission.Spec.RabbitmqClusterReference.Name).To(Equal("some-cluster"))
109+
})
110+
})
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package v1beta1
2+
3+
import (
4+
"fmt"
5+
6+
apierrors "k8s.io/apimachinery/pkg/api/errors"
7+
"k8s.io/apimachinery/pkg/runtime"
8+
"k8s.io/apimachinery/pkg/util/validation/field"
9+
ctrl "sigs.k8s.io/controller-runtime"
10+
logf "sigs.k8s.io/controller-runtime/pkg/log"
11+
"sigs.k8s.io/controller-runtime/pkg/webhook"
12+
)
13+
14+
// log is for logging in this package.
15+
var topicpermissionlog = logf.Log.WithName("topicpermission-resource")
16+
17+
func (r *TopicPermission) SetupWebhookWithManager(mgr ctrl.Manager) error {
18+
return ctrl.NewWebhookManagedBy(mgr).
19+
For(r).
20+
Complete()
21+
}
22+
23+
//+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}
24+
25+
var _ webhook.Validator = &TopicPermission{}
26+
27+
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
28+
func (p *TopicPermission) ValidateCreate() error {
29+
var errorList field.ErrorList
30+
if p.Spec.User == "" && p.Spec.UserReference == nil {
31+
errorList = append(errorList, field.Required(field.NewPath("spec", "user and userReference"),
32+
"must specify either spec.user or spec.userReference"))
33+
return apierrors.NewInvalid(GroupVersion.WithKind("Permission").GroupKind(), p.Name, errorList)
34+
}
35+
36+
if p.Spec.User != "" && p.Spec.UserReference != nil {
37+
errorList = append(errorList, field.Required(field.NewPath("spec", "user and userReference"),
38+
"cannot specify spec.user and spec.userReference at the same time"))
39+
return apierrors.NewInvalid(GroupVersion.WithKind("Permission").GroupKind(), p.Name, errorList)
40+
}
41+
return p.Spec.RabbitmqClusterReference.ValidateOnCreate(p.GroupResource(), p.Name)
42+
}
43+
44+
// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
45+
func (p *TopicPermission) ValidateUpdate(old runtime.Object) error {
46+
oldPermission, ok := old.(*TopicPermission)
47+
if !ok {
48+
return apierrors.NewBadRequest(fmt.Sprintf("expected a permission but got a %T", old))
49+
}
50+
51+
var errorList field.ErrorList
52+
if p.Spec.User == "" && p.Spec.UserReference == nil {
53+
errorList = append(errorList, field.Required(field.NewPath("spec", "user and userReference"),
54+
"must specify either spec.user or spec.userReference"))
55+
return apierrors.NewInvalid(GroupVersion.WithKind("TopicPermission").GroupKind(), p.Name, errorList)
56+
}
57+
58+
if p.Spec.User != "" && p.Spec.UserReference != nil {
59+
errorList = append(errorList, field.Required(field.NewPath("spec", "user and userReference"),
60+
"cannot specify spec.user and spec.userReference at the same time"))
61+
return apierrors.NewInvalid(GroupVersion.WithKind("TopicPermission").GroupKind(), p.Name, errorList)
62+
}
63+
64+
detailMsg := "updates on exchange, user, userReference, vhost and rabbitmqClusterReference are all forbidden"
65+
if p.Spec.Permissions.Exchange != oldPermission.Spec.Permissions.Exchange {
66+
return apierrors.NewForbidden(p.GroupResource(), p.Name,
67+
field.Forbidden(field.NewPath("spec", "permissions", "exchange"), detailMsg))
68+
}
69+
70+
if p.Spec.User != oldPermission.Spec.User {
71+
return apierrors.NewForbidden(p.GroupResource(), p.Name,
72+
field.Forbidden(field.NewPath("spec", "user"), detailMsg))
73+
}
74+
75+
if userReferenceUpdated(p.Spec.UserReference, oldPermission.Spec.UserReference) {
76+
return apierrors.NewForbidden(p.GroupResource(), p.Name,
77+
field.Forbidden(field.NewPath("spec", "userReference"), detailMsg))
78+
}
79+
80+
if p.Spec.Vhost != oldPermission.Spec.Vhost {
81+
return apierrors.NewForbidden(p.GroupResource(), p.Name,
82+
field.Forbidden(field.NewPath("spec", "vhost"), detailMsg))
83+
}
84+
85+
if !oldPermission.Spec.RabbitmqClusterReference.Matches(&p.Spec.RabbitmqClusterReference) {
86+
return apierrors.NewForbidden(p.GroupResource(), p.Name,
87+
field.Forbidden(field.NewPath("spec", "rabbitmqClusterReference"), detailMsg))
88+
}
89+
return nil
90+
}
91+
92+
// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
93+
func (r *TopicPermission) ValidateDelete() error {
94+
return nil
95+
}

0 commit comments

Comments
 (0)