Skip to content

Commit 9b36238

Browse files
authored
Merge pull request #177 from rabbitmq/user_permissions
Able to reference a User from permissions.rabbitmq.com crd
2 parents d90deec + 025435c commit 9b36238

19 files changed

+306
-84
lines changed

api/v1beta1/permission_types.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
package v1beta1
22

33
import (
4+
corev1 "k8s.io/api/core/v1"
45
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
56
"k8s.io/apimachinery/pkg/runtime/schema"
67
)
78

89
// PermissionSpec defines the desired state of Permission
910
type PermissionSpec struct {
10-
// Name of an existing user; required property; cannot be updated
11-
// +kubebuilder:validation:Required
12-
User string `json:"user"`
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"`
1315
// Name of an existing vhost; required property; cannot be updated
1416
// +kubebuilder:validation:Required
1517
Vhost string `json:"vhost"`

api/v1beta1/permission_types_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
. "github.com/onsi/ginkgo"
66
. "github.com/onsi/gomega"
7+
corev1 "k8s.io/api/core/v1"
78
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
89
"k8s.io/apimachinery/pkg/types"
910
)
@@ -77,4 +78,33 @@ var _ = Describe("Permission spec", func() {
7778
Expect(fetchedPermission.Spec.Permissions.Write).To(Equal(".*"))
7879
Expect(fetchedPermission.Spec.Permissions.Read).To(Equal("^?"))
7980
})
81+
82+
It("creates a permission object with user reference provided", func() {
83+
permission := Permission{
84+
ObjectMeta: metav1.ObjectMeta{
85+
Name: "user-ref-permission",
86+
Namespace: namespace,
87+
},
88+
Spec: PermissionSpec{
89+
UserReference: &corev1.LocalObjectReference{
90+
Name: "a-created-user",
91+
},
92+
Vhost: "/test",
93+
Permissions: VhostPermissions{},
94+
RabbitmqClusterReference: RabbitmqClusterReference{
95+
Name: "some-cluster",
96+
},
97+
},
98+
}
99+
Expect(k8sClient.Create(ctx, &permission)).To(Succeed())
100+
fetchedPermission := &Permission{}
101+
Expect(k8sClient.Get(ctx, types.NamespacedName{
102+
Name: permission.Name,
103+
Namespace: permission.Namespace,
104+
}, fetchedPermission)).To(Succeed())
105+
Expect(fetchedPermission.Spec.UserReference.Name).To(Equal("a-created-user"))
106+
Expect(fetchedPermission.Spec.User).To(Equal(""))
107+
Expect(fetchedPermission.Spec.Vhost).To(Equal("/test"))
108+
Expect(fetchedPermission.Spec.RabbitmqClusterReference.Name).To(Equal("some-cluster"))
109+
})
80110
})

api/v1beta1/permission_webhook.go

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package v1beta1
22

33
import (
44
"fmt"
5+
corev1 "k8s.io/api/core/v1"
56
apierrors "k8s.io/apimachinery/pkg/api/errors"
67
"k8s.io/apimachinery/pkg/runtime"
78
"k8s.io/apimachinery/pkg/util/validation/field"
@@ -19,25 +20,57 @@ func (p *Permission) SetupWebhookWithManager(mgr ctrl.Manager) error {
1920

2021
var _ webhook.Validator = &Permission{}
2122

22-
// no validation on create
23+
// ValidateCreate checks if only one of spec.user and spec.userReference is specified
2324
func (p *Permission) ValidateCreate() error {
25+
var errorList field.ErrorList
26+
if p.Spec.User == "" && p.Spec.UserReference == nil {
27+
errorList = append(errorList, field.Required(field.NewPath("spec", "user and userReference"),
28+
"must specify either spec.user or spec.userReference"))
29+
return apierrors.NewInvalid(GroupVersion.WithKind("Permission").GroupKind(), p.Name, errorList)
30+
}
31+
32+
if p.Spec.User != "" && p.Spec.UserReference != nil {
33+
errorList = append(errorList, field.Required(field.NewPath("spec", "user and userReference"),
34+
"cannot specify spec.user and spec.userReference at the same time"))
35+
return apierrors.NewInvalid(GroupVersion.WithKind("Permission").GroupKind(), p.Name, errorList)
36+
}
37+
2438
return nil
2539
}
2640

27-
// do not allow updates on spec.vhost, spec.user, and spec.rabbitmqClusterReference
41+
// ValidateUpdate do not allow updates on spec.vhost, spec.user, spec.userReference, and spec.rabbitmqClusterReference
2842
// updates on spec.permissions are allowed
43+
// only one of spec.user and spec.userReference can be specified
2944
func (p *Permission) ValidateUpdate(old runtime.Object) error {
3045
oldPermission, ok := old.(*Permission)
3146
if !ok {
3247
return apierrors.NewBadRequest(fmt.Sprintf("expected a permission but got a %T", old))
3348
}
3449

35-
detailMsg := "updates on user, vhost and rabbitmqClusterReference are all forbidden"
50+
var errorList field.ErrorList
51+
if p.Spec.User == "" && p.Spec.UserReference == nil {
52+
errorList = append(errorList, field.Required(field.NewPath("spec", "user and userReference"),
53+
"must specify either spec.user or spec.userReference"))
54+
return apierrors.NewInvalid(GroupVersion.WithKind("Permission").GroupKind(), p.Name, errorList)
55+
}
56+
57+
if p.Spec.User != "" && p.Spec.UserReference != nil {
58+
errorList = append(errorList, field.Required(field.NewPath("spec", "user and userReference"),
59+
"cannot specify spec.user and spec.userReference at the same time"))
60+
return apierrors.NewInvalid(GroupVersion.WithKind("Permission").GroupKind(), p.Name, errorList)
61+
}
62+
63+
detailMsg := "updates on user, userReference, vhost and rabbitmqClusterReference are all forbidden"
3664
if p.Spec.User != oldPermission.Spec.User {
3765
return apierrors.NewForbidden(p.GroupResource(), p.Name,
3866
field.Forbidden(field.NewPath("spec", "user"), detailMsg))
3967
}
4068

69+
if userReferenceUpdated(p.Spec.UserReference, oldPermission.Spec.UserReference) {
70+
return apierrors.NewForbidden(p.GroupResource(), p.Name,
71+
field.Forbidden(field.NewPath("spec", "userReference"), detailMsg))
72+
}
73+
4174
if p.Spec.Vhost != oldPermission.Spec.Vhost {
4275
return apierrors.NewForbidden(p.GroupResource(), p.Name,
4376
field.Forbidden(field.NewPath("spec", "vhost"), detailMsg))
@@ -50,7 +83,22 @@ func (p *Permission) ValidateUpdate(old runtime.Object) error {
5083
return nil
5184
}
5285

53-
// no validation on delete
86+
// returns true if userReference, which is a pointer to corev1.LocalObjectReference, has changed
87+
func userReferenceUpdated(new, old *corev1.LocalObjectReference) bool {
88+
if new == nil && old == nil {
89+
return false
90+
}
91+
if (new == nil && old != nil) ||
92+
(new != nil && old == nil) {
93+
return true
94+
}
95+
if new.Name != old.Name {
96+
return true
97+
}
98+
return false
99+
}
100+
101+
// ValidateDelete no validation on delete
54102
func (p *Permission) ValidateDelete() error {
55103
return nil
56104
}

api/v1beta1/permission_webhook_test.go

Lines changed: 70 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package v1beta1
33
import (
44
. "github.com/onsi/ginkgo"
55
. "github.com/onsi/gomega"
6+
corev1 "k8s.io/api/core/v1"
67
apierrors "k8s.io/apimachinery/pkg/api/errors"
78
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
89
)
@@ -26,41 +27,80 @@ var _ = Describe("permission webhook", func() {
2627
},
2728
}
2829

29-
It("does not allow updates on user", func() {
30-
newPermission := permission.DeepCopy()
31-
newPermission.Spec.User = "new-user"
32-
Expect(apierrors.IsForbidden(newPermission.ValidateUpdate(&permission))).To(BeTrue())
33-
})
30+
Context("ValidateUpdate", func() {
31+
It("does not allow updates on user", func() {
32+
newPermission := permission.DeepCopy()
33+
newPermission.Spec.User = "new-user"
34+
Expect(apierrors.IsForbidden(newPermission.ValidateUpdate(&permission))).To(BeTrue())
35+
})
3436

35-
It("does not allow updates on vhost", func() {
36-
newPermission := permission.DeepCopy()
37-
newPermission.Spec.Vhost = "new-vhost"
38-
Expect(apierrors.IsForbidden(newPermission.ValidateUpdate(&permission))).To(BeTrue())
39-
})
37+
It("does not allow updates on userReference", func() {
38+
permissionWithUserRef := permission.DeepCopy()
39+
permissionWithUserRef.Spec.User = ""
40+
permissionWithUserRef.Spec.UserReference = &corev1.LocalObjectReference{Name: "a-user"}
41+
newPermission := permissionWithUserRef.DeepCopy()
42+
newPermission.Spec.UserReference = &corev1.LocalObjectReference{Name: "a-new-user"}
43+
Expect(apierrors.IsForbidden(newPermission.ValidateUpdate(permissionWithUserRef))).To(BeTrue())
44+
})
4045

41-
It("does not allow updates on RabbitmqClusterReference", func() {
42-
newPermission := permission.DeepCopy()
43-
newPermission.Spec.RabbitmqClusterReference = RabbitmqClusterReference{
44-
Name: "new-cluster",
45-
}
46-
Expect(apierrors.IsForbidden(newPermission.ValidateUpdate(&permission))).To(BeTrue())
47-
})
46+
It("does not allow updates on vhost", func() {
47+
newPermission := permission.DeepCopy()
48+
newPermission.Spec.Vhost = "new-vhost"
49+
Expect(apierrors.IsForbidden(newPermission.ValidateUpdate(&permission))).To(BeTrue())
50+
})
4851

49-
It("allows updates on permission.spec.permissions.configure", func() {
50-
newPermission := permission.DeepCopy()
51-
newPermission.Spec.Permissions.Configure = "?"
52-
Expect(newPermission.ValidateUpdate(&permission)).To(Succeed())
53-
})
52+
It("does not allow updates on RabbitmqClusterReference", func() {
53+
newPermission := permission.DeepCopy()
54+
newPermission.Spec.RabbitmqClusterReference = RabbitmqClusterReference{
55+
Name: "new-cluster",
56+
}
57+
Expect(apierrors.IsForbidden(newPermission.ValidateUpdate(&permission))).To(BeTrue())
58+
})
59+
60+
It("allows updates on permission.spec.permissions.configure", func() {
61+
newPermission := permission.DeepCopy()
62+
newPermission.Spec.Permissions.Configure = "?"
63+
Expect(newPermission.ValidateUpdate(&permission)).To(Succeed())
64+
})
65+
66+
It("allows updates on permission.spec.permissions.read", func() {
67+
newPermission := permission.DeepCopy()
68+
newPermission.Spec.Permissions.Read = "?"
69+
Expect(newPermission.ValidateUpdate(&permission)).To(Succeed())
70+
})
71+
72+
It("allows updates on permission.spec.permissions.write", func() {
73+
newPermission := permission.DeepCopy()
74+
newPermission.Spec.Permissions.Write = "?"
75+
Expect(newPermission.ValidateUpdate(&permission)).To(Succeed())
76+
})
77+
78+
It("does not allow user and userReference to be specified at the same time", func() {
79+
newPermission := permission.DeepCopy()
80+
newPermission.Spec.UserReference = &corev1.LocalObjectReference{Name: "invalid"}
81+
Expect(apierrors.IsInvalid(newPermission.ValidateUpdate(&permission))).To(BeTrue())
82+
})
5483

55-
It("allows updates on permission.spec.permissions.read", func() {
56-
newPermission := permission.DeepCopy()
57-
newPermission.Spec.Permissions.Read = "?"
58-
Expect(newPermission.ValidateUpdate(&permission)).To(Succeed())
84+
It("does not allow both user and userReference to be unset", func() {
85+
newPermission := permission.DeepCopy()
86+
newPermission.Spec.User = ""
87+
newPermission.Spec.UserReference = nil
88+
Expect(apierrors.IsInvalid(newPermission.ValidateUpdate(&permission))).To(BeTrue())
89+
})
5990
})
6091

61-
It("allows updates on permission.spec.permissions.write", func() {
62-
newPermission := permission.DeepCopy()
63-
newPermission.Spec.Permissions.Write = "?"
64-
Expect(newPermission.ValidateUpdate(&permission)).To(Succeed())
92+
Context("ValidateCreate", func() {
93+
It("does not allow user and userReference to be specified at the same time", func() {
94+
invalidPermission := permission.DeepCopy()
95+
invalidPermission.Spec.UserReference = &corev1.LocalObjectReference{Name: "invalid"}
96+
invalidPermission.Spec.User = "test-user"
97+
Expect(apierrors.IsInvalid(invalidPermission.ValidateCreate())).To(BeTrue())
98+
})
99+
It("does not allow both user and userReference to be unset", func() {
100+
invalidPermission := permission.DeepCopy()
101+
invalidPermission.Spec.UserReference = nil
102+
invalidPermission.Spec.User = ""
103+
Expect(apierrors.IsInvalid(invalidPermission.ValidateCreate())).To(BeTrue())
104+
})
65105
})
66106
})

api/v1beta1/zz_generated.deepcopy.go

Lines changed: 6 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/rabbitmq.com_permissions.yaml

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,17 +60,26 @@ spec:
6060
- name
6161
type: object
6262
user:
63-
description: Name of an existing user; required property; cannot be
64-
updated
63+
description: Name of an existing user; must provide user or userReference,
64+
else create/update will fail; cannot be updated
6565
type: string
66+
userReference:
67+
description: Reference to an existing user.rabbitmq.com object; must
68+
provide user or userReference, else create/update will fail; cannot
69+
be updated
70+
properties:
71+
name:
72+
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
73+
TODO: Add other useful fields. apiVersion, kind, uid?'
74+
type: string
75+
type: object
6676
vhost:
6777
description: Name of an existing vhost; required property; cannot
6878
be updated
6979
type: string
7080
required:
7181
- permissions
7282
- rabbitmqClusterReference
73-
- user
7483
- vhost
7584
type: object
7685
status:

controllers/binding_controller_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -190,9 +190,10 @@ var _ = Describe("bindingController", func() {
190190
err := client.Get(ctx, types.NamespacedName{Name: binding.Name, Namespace: binding.Namespace}, &topology.Binding{})
191191
return apierrors.IsNotFound(err)
192192
}, 5).Should(BeTrue())
193-
observedEvents := observedEvents()
194-
Expect(observedEvents).NotTo(ContainElement("Warning FailedDelete failed to delete binding"))
195-
Expect(observedEvents).To(ContainElement("Normal SuccessfulDelete successfully deleted binding"))
193+
Expect(observedEvents()).To(SatisfyAll(
194+
Not(ContainElement("Warning FailedDelete failed to delete binding")),
195+
ContainElement("Normal SuccessfulDelete successfully deleted binding"),
196+
))
196197
})
197198
})
198199
})

controllers/exchange_controller_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -190,9 +190,10 @@ var _ = Describe("exchange-controller", func() {
190190
err := client.Get(ctx, types.NamespacedName{Name: exchange.Name, Namespace: exchange.Namespace}, &topology.Exchange{})
191191
return apierrors.IsNotFound(err)
192192
}, 5).Should(BeTrue())
193-
observedEvents := observedEvents()
194-
Expect(observedEvents).NotTo(ContainElement("Warning FailedDelete failed to delete exchange"))
195-
Expect(observedEvents).To(ContainElement("Normal SuccessfulDelete successfully deleted exchange"))
193+
Expect(observedEvents()).To(SatisfyAll(
194+
Not(ContainElement("Warning FailedDelete failed to delete exchange")),
195+
ContainElement("Normal SuccessfulDelete successfully deleted exchange"),
196+
))
196197
})
197198
})
198199
})

controllers/federation_controller_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,9 +193,10 @@ var _ = Describe("federation-controller", func() {
193193
err := client.Get(ctx, types.NamespacedName{Name: federation.Name, Namespace: federation.Namespace}, &topology.Federation{})
194194
return apierrors.IsNotFound(err)
195195
}, 5).Should(BeTrue())
196-
observedEvents := observedEvents()
197-
Expect(observedEvents).NotTo(ContainElement("Warning FailedDelete failed to delete federation upstream parameter"))
198-
Expect(observedEvents).To(ContainElement("Normal SuccessfulDelete successfully deleted federation upstream parameter"))
196+
Expect(observedEvents()).To(SatisfyAll(
197+
Not(ContainElement("Warning FailedDelete failed to delete federation upstream parameter")),
198+
ContainElement("Normal SuccessfulDelete successfully deleted federation upstream parameter"),
199+
))
199200
})
200201
})
201202
})

0 commit comments

Comments
 (0)