Skip to content

Commit 46ad859

Browse files
authored
feat: add webhook validation (#62)
1 parent 08bf927 commit 46ad859

23 files changed

+725
-41
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,6 @@ test/e2e/data/infrastructure-oci/v1beta1/cluster-template-bare-metal.yaml
4545
test/e2e/data/infrastructure-oci/v1beta1/cluster-template-custom-networking-seclist.yaml
4646
test/e2e/data/infrastructure-oci/v1beta1/cluster-template-custom-networking-nsg.yaml
4747
test/e2e/data/infrastructure-oci/v1beta1/cluster-template-multiple-node-nsg.yaml
48+
test/e2e/data/infrastructure-oci/v1beta1/cluster-template-local-vcn-peering.yaml
49+
test/e2e/data/infrastructure-oci/v1beta1/cluster-template-cluster-class.yaml
50+
test/e2e/data/infrastructure-oci/v1beta1/cluster-template-remote-vcn-peering.yaml

api/v1beta1/ocicluster_webhook.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
*
3+
* Copyright (c) 2022, Oracle and/or its affiliates.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
* /
17+
*
18+
*/
19+
20+
package v1beta1
21+
22+
import (
23+
"fmt"
24+
25+
apierrors "k8s.io/apimachinery/pkg/api/errors"
26+
"k8s.io/apimachinery/pkg/runtime"
27+
"k8s.io/apimachinery/pkg/util/validation/field"
28+
29+
ctrl "sigs.k8s.io/controller-runtime"
30+
"sigs.k8s.io/controller-runtime/pkg/webhook"
31+
)
32+
33+
var clusterlogger = ctrl.Log.WithName("ocicluster-resource")
34+
35+
var (
36+
_ webhook.Validator = &OCICluster{}
37+
)
38+
39+
// +kubebuilder:webhook:verbs=create;update,path=/validate-infrastructure-cluster-x-k8s-io-v1beta1-ocicluster,mutating=false,failurePolicy=fail,matchPolicy=Equivalent,groups=infrastructure.cluster.x-k8s.io,resources=ociclusters,versions=v1beta1,name=validation.ocicluster.infrastructure.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1beta1
40+
41+
func (c *OCICluster) SetupWebhookWithManager(mgr ctrl.Manager) error {
42+
return ctrl.NewWebhookManagedBy(mgr).
43+
For(c).
44+
Complete()
45+
}
46+
47+
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type.
48+
func (c *OCICluster) ValidateCreate() error {
49+
clusterlogger.Info("validate update cluster", "name", c.Name)
50+
51+
var allErrs field.ErrorList
52+
53+
allErrs = append(allErrs, c.validate()...)
54+
55+
if len(allErrs) == 0 {
56+
return nil
57+
}
58+
59+
return apierrors.NewInvalid(c.GroupVersionKind().GroupKind(), c.Name, allErrs)
60+
}
61+
62+
// ValidateDelete implements webhook.Validator so a webhook will be registered for the type.
63+
func (c *OCICluster) ValidateDelete() error {
64+
clusterlogger.Info("validate delete cluster", "name", c.Name)
65+
66+
return nil
67+
}
68+
69+
// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type.
70+
func (c *OCICluster) ValidateUpdate(old runtime.Object) error {
71+
clusterlogger.Info("validate update cluster", "name", c.Name)
72+
73+
var allErrs field.ErrorList
74+
75+
oldCluster, ok := old.(*OCICluster)
76+
if !ok {
77+
return apierrors.NewBadRequest(fmt.Sprintf("expected an OCICluster but got a %T", old))
78+
}
79+
80+
if c.Spec.Region != oldCluster.Spec.Region {
81+
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "region"), c.Spec.Region, "field is immutable"))
82+
}
83+
84+
allErrs = append(allErrs, c.validate()...)
85+
86+
if len(allErrs) == 0 {
87+
return nil
88+
}
89+
90+
return apierrors.NewInvalid(c.GroupVersionKind().GroupKind(), c.Name, allErrs)
91+
}
92+
93+
func (c *OCICluster) validate() field.ErrorList {
94+
var allErrs field.ErrorList
95+
96+
// simple validity test for compartment
97+
if !validOcid(c.Spec.CompartmentId) {
98+
allErrs = append(
99+
allErrs,
100+
field.Invalid(field.NewPath("spec", "compartmentId"), c.Spec.CompartmentId, "field is invalid"))
101+
}
102+
103+
if len(allErrs) == 0 {
104+
return nil
105+
}
106+
107+
return allErrs
108+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
*
3+
* Copyright (c) 2022, Oracle and/or its affiliates.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
* /
17+
*
18+
*/
19+
20+
package v1beta1
21+
22+
import (
23+
"testing"
24+
25+
"github.com/onsi/gomega"
26+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
)
28+
29+
func TestOCICluster_ValidateCreate(t *testing.T) {
30+
31+
tests := []struct {
32+
name string
33+
c *OCICluster
34+
expectErr bool
35+
}{
36+
{
37+
name: "shouldn't allow bad ImageId",
38+
c: &OCICluster{
39+
ObjectMeta: metav1.ObjectMeta{},
40+
Spec: OCIClusterSpec{
41+
CompartmentId: "badocid",
42+
},
43+
},
44+
expectErr: true,
45+
},
46+
{
47+
name: "should succeed",
48+
c: &OCICluster{
49+
ObjectMeta: metav1.ObjectMeta{},
50+
Spec: OCIClusterSpec{
51+
CompartmentId: "ocid",
52+
},
53+
},
54+
expectErr: false,
55+
},
56+
}
57+
for _, test := range tests {
58+
t.Run(test.name, func(t *testing.T) {
59+
g := gomega.NewWithT(t)
60+
61+
if test.expectErr {
62+
g.Expect(test.c.ValidateCreate()).NotTo(gomega.Succeed())
63+
} else {
64+
g.Expect(test.c.ValidateCreate()).To(gomega.Succeed())
65+
}
66+
})
67+
}
68+
}
69+
70+
func TestOCICluster_ValidateUpdate(t *testing.T) {
71+
tests := []struct {
72+
name string
73+
c *OCICluster
74+
old *OCICluster
75+
expectErr bool
76+
}{
77+
{
78+
name: "shouldn't region change",
79+
c: &OCICluster{
80+
ObjectMeta: metav1.ObjectMeta{},
81+
Spec: OCIClusterSpec{
82+
Region: "new-region",
83+
},
84+
},
85+
old: &OCICluster{
86+
ObjectMeta: metav1.ObjectMeta{},
87+
Spec: OCIClusterSpec{
88+
Region: "old-region",
89+
},
90+
},
91+
expectErr: true,
92+
},
93+
{
94+
name: "should succeed",
95+
c: &OCICluster{
96+
ObjectMeta: metav1.ObjectMeta{},
97+
Spec: OCIClusterSpec{
98+
CompartmentId: "ocid",
99+
Region: "old-region",
100+
},
101+
},
102+
old: &OCICluster{
103+
ObjectMeta: metav1.ObjectMeta{},
104+
Spec: OCIClusterSpec{
105+
Region: "old-region",
106+
},
107+
},
108+
expectErr: false,
109+
},
110+
}
111+
112+
for _, test := range tests {
113+
t.Run(test.name, func(t *testing.T) {
114+
g := gomega.NewWithT(t)
115+
116+
if test.expectErr {
117+
g.Expect(test.c.ValidateUpdate(test.old)).NotTo(gomega.Succeed())
118+
} else {
119+
g.Expect(test.c.ValidateUpdate(test.old)).To(gomega.Succeed())
120+
}
121+
})
122+
}
123+
124+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
*
3+
* Copyright (c) 2022, Oracle and/or its affiliates.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
* /
17+
*
18+
*/
19+
20+
package v1beta1
21+
22+
import (
23+
apierrors "k8s.io/apimachinery/pkg/api/errors"
24+
"k8s.io/apimachinery/pkg/runtime"
25+
"k8s.io/apimachinery/pkg/util/validation/field"
26+
ctrl "sigs.k8s.io/controller-runtime"
27+
"sigs.k8s.io/controller-runtime/pkg/webhook"
28+
)
29+
30+
var (
31+
_ webhook.Validator = &OCIMachineTemplate{}
32+
)
33+
34+
// +kubebuilder:webhook:verbs=create;update,path=/validate-infrastructure-cluster-x-k8s-io-v1beta1-ocimachinetemplate,mutating=false,failurePolicy=fail,matchPolicy=Equivalent,groups=infrastructure.cluster.x-k8s.io,resources=ocimachinetemplates,versions=v1beta1,name=validation.ocimachinetemplate.infrastructure.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1beta1
35+
36+
func (m *OCIMachineTemplate) SetupWebhookWithManager(mgr ctrl.Manager) error {
37+
return ctrl.NewWebhookManagedBy(mgr).
38+
For(m).
39+
Complete()
40+
}
41+
42+
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type.
43+
func (m *OCIMachineTemplate) ValidateCreate() error {
44+
clusterlogger.Info("validate create machinetemplate", "name", m.Name)
45+
46+
var allErrs field.ErrorList
47+
48+
allErrs = append(allErrs, m.validate()...)
49+
50+
if len(allErrs) == 0 {
51+
return nil
52+
}
53+
54+
return apierrors.NewInvalid(m.GroupVersionKind().GroupKind(), m.Name, allErrs)
55+
}
56+
57+
// ValidateDelete implements webhook.Validator so a webhook will be registered for the type.
58+
func (m *OCIMachineTemplate) ValidateDelete() error {
59+
clusterlogger.Info("validate delete machinetemplate", "name", m.Name)
60+
61+
return nil
62+
}
63+
64+
// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type.
65+
func (m *OCIMachineTemplate) ValidateUpdate(old runtime.Object) error {
66+
clusterlogger.Info("validate update machinetemplate", "name", m.Name)
67+
68+
var allErrs field.ErrorList
69+
70+
allErrs = append(allErrs, m.validate()...)
71+
72+
if len(allErrs) == 0 {
73+
return nil
74+
}
75+
76+
return apierrors.NewInvalid(m.GroupVersionKind().GroupKind(), m.Name, allErrs)
77+
}
78+
79+
func (m *OCIMachineTemplate) validate() field.ErrorList {
80+
var allErrs field.ErrorList
81+
82+
if len(m.Spec.Template.Spec.ImageId) > 0 && !validOcid(m.Spec.Template.Spec.ImageId) {
83+
allErrs = append(
84+
allErrs,
85+
field.Invalid(
86+
field.NewPath("Spec", "Template", "Spec", "imageId"),
87+
m.Spec.Template.Spec.ImageId,
88+
"field is invalid"),
89+
)
90+
}
91+
92+
// simple validity test for compartment
93+
if len(m.Spec.Template.Spec.CompartmentId) > 0 && !validOcid(m.Spec.Template.Spec.CompartmentId) {
94+
allErrs = append(
95+
allErrs,
96+
field.Invalid(
97+
field.NewPath("Spec", "Template", "Spec", "compartmentId"),
98+
m.Spec.Template.Spec.CompartmentId,
99+
"field is invalid"),
100+
)
101+
}
102+
103+
if len(allErrs) == 0 {
104+
return nil
105+
}
106+
107+
return allErrs
108+
}

0 commit comments

Comments
 (0)