Skip to content

Commit 547257e

Browse files
committed
Only allow a single ctlplane per namespace
Signed-off-by: Martin Schuppert <[email protected]>
1 parent 9351c6f commit 547257e

File tree

2 files changed

+75
-0
lines changed

2 files changed

+75
-0
lines changed

apis/core/v1beta1/openstackcontrolplane_webhook.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package v1beta1
1818

1919
import (
20+
"context"
2021
"fmt"
2122
"strings"
2223

@@ -26,11 +27,14 @@ import (
2627
"k8s.io/apimachinery/pkg/runtime/schema"
2728
"k8s.io/apimachinery/pkg/util/validation/field"
2829
ctrl "sigs.k8s.io/controller-runtime"
30+
"sigs.k8s.io/controller-runtime/pkg/client"
2931
logf "sigs.k8s.io/controller-runtime/pkg/log"
3032
"sigs.k8s.io/controller-runtime/pkg/webhook"
3133
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
3234
)
3335

36+
var ctlplaneWebhookClient client.Client
37+
3438
// OpenStackControlPlaneDefaults -
3539
type OpenStackControlPlaneDefaults struct {
3640
RabbitMqImageURL string
@@ -49,6 +53,10 @@ func SetupOpenStackControlPlaneDefaults(defaults OpenStackControlPlaneDefaults)
4953

5054
// SetupWebhookWithManager sets up the Webhook with the Manager.
5155
func (r *OpenStackControlPlane) SetupWebhookWithManager(mgr ctrl.Manager) error {
56+
if ctlplaneWebhookClient == nil {
57+
ctlplaneWebhookClient = mgr.GetClient()
58+
}
59+
5260
return ctrl.NewWebhookManagedBy(mgr).
5361
For(r).
5462
Complete()
@@ -64,6 +72,38 @@ func (r *OpenStackControlPlane) ValidateCreate() (admission.Warnings, error) {
6472

6573
var allErrs field.ErrorList
6674
basePath := field.NewPath("spec")
75+
76+
ctlplaneList := &OpenStackControlPlaneList{}
77+
listOpts := []client.ListOption{
78+
client.InNamespace(r.Namespace),
79+
}
80+
if err := ctlplaneWebhookClient.List(context.TODO(), ctlplaneList, listOpts...); err != nil {
81+
return nil, apierrors.NewForbidden(
82+
schema.GroupResource{
83+
Group: GroupVersion.WithKind("OpenStackControlPlane").Group,
84+
Resource: GroupVersion.WithKind("OpenStackControlPlane").Kind,
85+
}, r.GetName(), &field.Error{
86+
Type: field.ErrorTypeForbidden,
87+
Field: "",
88+
BadValue: r.Name,
89+
Detail: err.Error(),
90+
},
91+
)
92+
}
93+
if len(ctlplaneList.Items) >= 1 {
94+
return nil, apierrors.NewForbidden(
95+
schema.GroupResource{
96+
Group: GroupVersion.WithKind("OpenStackControlPlane").Group,
97+
Resource: GroupVersion.WithKind("OpenStackControlPlane").Kind,
98+
}, r.GetName(), &field.Error{
99+
Type: field.ErrorTypeForbidden,
100+
Field: "",
101+
BadValue: r.Name,
102+
Detail: "Only one OpenStackControlPlane instance per namespace is supported at this time.",
103+
},
104+
)
105+
}
106+
67107
if err := r.ValidateCreateServices(basePath); err != nil {
68108
allErrs = append(allErrs, err...)
69109
}

tests/functional/openstackoperator_controller_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1673,6 +1673,41 @@ var _ = Describe("OpenStackOperator controller", func() {
16731673

16741674
var _ = Describe("OpenStackOperator Webhook", func() {
16751675

1676+
It("Blocks creating multiple ctlplane CRs in the same namespace", func() {
1677+
spec := GetDefaultOpenStackControlPlaneSpec()
1678+
spec["tls"] = GetTLSPublicSpec()
1679+
DeferCleanup(
1680+
th.DeleteInstance,
1681+
CreateOpenStackControlPlane(names.OpenStackControlplaneName, spec),
1682+
)
1683+
1684+
OSCtlplane := GetOpenStackControlPlane(names.OpenStackControlplaneName)
1685+
Expect(OSCtlplane.Labels).Should(Not(BeNil()))
1686+
Expect(OSCtlplane.Labels).Should(HaveKeyWithValue("core.openstack.org/openstackcontrolplane", ""))
1687+
1688+
raw := map[string]interface{}{
1689+
"apiVersion": "core.openstack.org/v1beta1",
1690+
"kind": "OpenStackControlPlane",
1691+
"metadata": map[string]interface{}{
1692+
"name": "foo",
1693+
"namespace": OSCtlplane.GetNamespace(),
1694+
},
1695+
"spec": spec,
1696+
}
1697+
1698+
unstructuredObj := &unstructured.Unstructured{Object: raw}
1699+
_, err := controllerutil.CreateOrPatch(
1700+
th.Ctx, th.K8sClient, unstructuredObj, func() error { return nil })
1701+
Expect(err).Should(HaveOccurred())
1702+
var statusError *k8s_errors.StatusError
1703+
Expect(errors.As(err, &statusError)).To(BeTrue())
1704+
Expect(statusError.ErrStatus.Details.Kind).To(Equal("OpenStackControlPlane"))
1705+
Expect(statusError.ErrStatus.Message).To(
1706+
ContainSubstring(
1707+
"Forbidden: Only one OpenStackControlPlane instance per namespace is supported at this time."),
1708+
)
1709+
})
1710+
16761711
It("Adds default label via defaulting webhook", func() {
16771712
spec := GetDefaultOpenStackControlPlaneSpec()
16781713
spec["tls"] = GetTLSPublicSpec()

0 commit comments

Comments
 (0)