Skip to content

Commit e0e6b42

Browse files
committed
Add notificationBusInstance webhook validation
Because we can't control user input, this patch introduces a webhook function to validate what we expect users to set as notificationsBusInstance top-level parameter. It also adds the webhook associated envTest to validate the use cases covered by the new function. Signed-off-by: Francesco Pantano <[email protected]>
1 parent aa5c12c commit e0e6b42

File tree

5 files changed

+84
-0
lines changed

5 files changed

+84
-0
lines changed

apis/core/v1beta1/openstackcontrolplane_webhook.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,10 @@ func (r *OpenStackControlPlane) ValidateCreate() (admission.Warnings, error) {
132132
allErrs = append(allErrs, err)
133133
}
134134

135+
if err := r.ValidateNotificationsBusInstance(basePath); err != nil {
136+
allErrs = append(allErrs, err)
137+
}
138+
135139
if len(allErrs) != 0 {
136140
return allWarn, apierrors.NewInvalid(
137141
schema.GroupKind{Group: "core.openstack.org", Kind: "OpenStackControlPlane"},
@@ -161,6 +165,10 @@ func (r *OpenStackControlPlane) ValidateUpdate(old runtime.Object) (admission.Wa
161165
allErrs = append(allErrs, err)
162166
}
163167

168+
if err := r.ValidateNotificationsBusInstance(basePath); err != nil {
169+
allErrs = append(allErrs, err)
170+
}
171+
164172
if len(allErrs) != 0 {
165173
return nil, apierrors.NewInvalid(
166174
schema.GroupKind{Group: "core.openstack.org", Kind: "OpenStackControlPlane"},
@@ -1138,3 +1146,28 @@ func (r *OpenStackControlPlane) ValidateTopology(basePath *field.Path) *field.Er
11381146
}
11391147
return nil
11401148
}
1149+
1150+
// ValidateNotificationsBusInstance - returns an error if the notificationsBusInstance
1151+
// parameter is not valid.
1152+
// - nil or empty string must be raised as an error
1153+
// - when notificationsBusInstance does not point to an existing RabbitMQ instance
1154+
func (r *OpenStackControlPlane) ValidateNotificationsBusInstance(basePath *field.Path) *field.Error {
1155+
notificationsField := basePath.Child("notificationsBusInstance")
1156+
// no notificationsBusInstance field set, nothing to validate here
1157+
if r.Spec.NotificationsBusInstance == nil {
1158+
return nil
1159+
}
1160+
// When NotificationsBusInstance is set, fail if it is an empty string
1161+
if *r.Spec.NotificationsBusInstance == "" {
1162+
return field.Invalid(notificationsField, *r.Spec.NotificationsBusInstance, "notificationsBusInstance is not a valid string")
1163+
}
1164+
// NotificationsBusInstance is set and must be equal to an existing
1165+
// deployed rabbitmq instance, otherwise we should fail because it
1166+
// does not represent a valid string
1167+
for k := range(*r.Spec.Rabbitmq.Templates) {
1168+
if *r.Spec.NotificationsBusInstance == k {
1169+
return nil
1170+
}
1171+
}
1172+
return field.Invalid(notificationsField, *r.Spec.NotificationsBusInstance, "notificationsBusInstance must match an existing RabbitMQ instance name")
1173+
}

pkg/openstack/neutron.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,12 @@ func ReconcileNeutron(ctx context.Context, instance *corev1beta1.OpenStackContro
155155
instance.Spec.Neutron.Template.TopologyRef = instance.Spec.TopologyRef
156156
}
157157

158+
// When no NotificationsBusInstance is referenced in the subCR (override)
159+
// try to inject the top-level one if defined
160+
if instance.Spec.Neutron.Template.NotificationsBusInstance == nil {
161+
instance.Spec.Neutron.Template.NotificationsBusInstance = instance.Spec.NotificationsBusInstance
162+
}
163+
158164
Log.Info("Reconciling NeutronAPI", "NeutronAPI.Namespace", instance.Namespace, "NeutronAPI.Name", "neutron")
159165
op, err := controllerutil.CreateOrPatch(ctx, helper.GetClient(), neutronAPI, func() error {
160166
instance.Spec.Neutron.Template.DeepCopyInto(&neutronAPI.Spec.NeutronAPISpecCore)

pkg/openstack/nova.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,12 @@ func ReconcileNova(ctx context.Context, instance *corev1beta1.OpenStackControlPl
7272
instance.Spec.Nova.Template.NodeSelector = &instance.Spec.NodeSelector
7373
}
7474

75+
// When no NotificationsBusInstance is referenced in the subCR (override)
76+
// try to inject the top-level one if defined
77+
if instance.Spec.Nova.Template.NotificationsBusInstance == nil {
78+
instance.Spec.Nova.Template.NotificationsBusInstance = instance.Spec.NotificationsBusInstance
79+
}
80+
7581
// When there's no Topology referenced in the Service Template, inject the
7682
// top-level one
7783
// NOTE: This does not check the Service subCRs: by default the generated

pkg/openstack/watcher.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,12 @@ func ReconcileWatcher(ctx context.Context, instance *corev1beta1.OpenStackContro
114114
instance.Spec.Watcher.Template.TopologyRef = instance.Spec.TopologyRef
115115
}
116116

117+
// When no NotificationsBusInstance is referenced in the subCR (override)
118+
// try to inject the top-level one if defined
119+
if instance.Spec.Watcher.Template.NotificationsBusInstance == nil {
120+
instance.Spec.Watcher.Template.NotificationsBusInstance = instance.Spec.NotificationsBusInstance
121+
}
122+
117123
helper.GetLogger().Info("Reconciling Watcher", "Watcher.Namespace", instance.Namespace, "Watcher.Name", "watcher")
118124
op, err := controllerutil.CreateOrPatch(ctx, helper.GetClient(), watcher, func() error {
119125
instance.Spec.Watcher.Template.DeepCopyInto(&watcher.Spec.WatcherSpecCore)

tests/functional/ctlplane/openstackoperator_controller_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2835,6 +2835,39 @@ var _ = Describe("OpenStackOperator controller", func() {
28352835

28362836
var _ = Describe("OpenStackOperator Webhook", func() {
28372837

2838+
DescribeTable("notificationsBusInstance",
2839+
func(getNotificationField func() (string, string)) {
2840+
spec := GetDefaultOpenStackControlPlaneSpec()
2841+
value, errMsg := getNotificationField()
2842+
spec["notificationsBusInstance"] = value
2843+
raw := map[string]interface{}{
2844+
"apiVersion": "core.openstack.org/v1beta1",
2845+
"kind": "OpenStackControlPlane",
2846+
"metadata": map[string]interface{}{
2847+
"name": "foo",
2848+
"namespace": namespace,
2849+
},
2850+
"spec": spec,
2851+
}
2852+
unstructuredObj := &unstructured.Unstructured{Object: raw}
2853+
_, err := controllerutil.CreateOrPatch(
2854+
th.Ctx, th.K8sClient, unstructuredObj, func() error { return nil })
2855+
Expect(err).Should(HaveOccurred())
2856+
var statusError *k8s_errors.StatusError
2857+
Expect(errors.As(err, &statusError)).To(BeTrue())
2858+
Expect(statusError.ErrStatus.Details.Kind).To(Equal("OpenStackControlPlane"))
2859+
Expect(statusError.ErrStatus.Message).To(
2860+
ContainSubstring(errMsg),
2861+
)
2862+
},
2863+
Entry("notificationsBusInstance is wrong", func() (string, string) {
2864+
return "foo", "spec.notificationsBusInstance: Invalid value: \"foo\": notificationsBusInstance must match an existing RabbitMQ instance name"
2865+
}),
2866+
Entry("notificationsBusInstance is an empty string", func() (string, string) {
2867+
return "", "spec.notificationsBusInstance: Invalid value: \"\": notificationsBusInstance is not a valid string"
2868+
}),
2869+
)
2870+
28382871
It("Blocks creating multiple ctlplane CRs in the same namespace", func() {
28392872
spec := GetDefaultOpenStackControlPlaneSpec()
28402873
spec["tls"] = GetTLSPublicSpec()

0 commit comments

Comments
 (0)