Skip to content

Commit

Permalink
feat: add webhook validation
Browse files Browse the repository at this point in the history
  • Loading branch information
joekr committed Apr 22, 2022
1 parent 08bf927 commit f459f94
Show file tree
Hide file tree
Showing 23 changed files with 725 additions and 41 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,6 @@ test/e2e/data/infrastructure-oci/v1beta1/cluster-template-bare-metal.yaml
test/e2e/data/infrastructure-oci/v1beta1/cluster-template-custom-networking-seclist.yaml
test/e2e/data/infrastructure-oci/v1beta1/cluster-template-custom-networking-nsg.yaml
test/e2e/data/infrastructure-oci/v1beta1/cluster-template-multiple-node-nsg.yaml
test/e2e/data/infrastructure-oci/v1beta1/cluster-template-local-vcn-peering.yaml
test/e2e/data/infrastructure-oci/v1beta1/cluster-template-cluster-class.yaml
test/e2e/data/infrastructure-oci/v1beta1/cluster-template-remote-vcn-peering.yaml
108 changes: 108 additions & 0 deletions api/v1beta1/ocicluster_webhook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
*
* Copyright (c) 2022, Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* /
*
*/

package v1beta1

import (
"fmt"

apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"

ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/webhook"
)

var clusterlogger = ctrl.Log.WithName("ocicluster-resource")

var (
_ webhook.Validator = &OCICluster{}
)

// +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

func (c *OCICluster) SetupWebhookWithManager(mgr ctrl.Manager) error {
return ctrl.NewWebhookManagedBy(mgr).
For(c).
Complete()
}

// ValidateCreate implements webhook.Validator so a webhook will be registered for the type.
func (c *OCICluster) ValidateCreate() error {
clusterlogger.Info("validate update cluster", "name", c.Name)

var allErrs field.ErrorList

allErrs = append(allErrs, c.validate()...)

if len(allErrs) == 0 {
return nil
}

return apierrors.NewInvalid(c.GroupVersionKind().GroupKind(), c.Name, allErrs)
}

// ValidateDelete implements webhook.Validator so a webhook will be registered for the type.
func (c *OCICluster) ValidateDelete() error {
clusterlogger.Info("validate delete cluster", "name", c.Name)

return nil
}

// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type.
func (c *OCICluster) ValidateUpdate(old runtime.Object) error {
clusterlogger.Info("validate update cluster", "name", c.Name)

var allErrs field.ErrorList

oldCluster, ok := old.(*OCICluster)
if !ok {
return apierrors.NewBadRequest(fmt.Sprintf("expected an OCICluster but got a %T", old))
}

if c.Spec.Region != oldCluster.Spec.Region {
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "region"), c.Spec.Region, "field is immutable"))
}

allErrs = append(allErrs, c.validate()...)

if len(allErrs) == 0 {
return nil
}

return apierrors.NewInvalid(c.GroupVersionKind().GroupKind(), c.Name, allErrs)
}

func (c *OCICluster) validate() field.ErrorList {
var allErrs field.ErrorList

// simple validity test for compartment
if !validOcid(c.Spec.CompartmentId) {
allErrs = append(
allErrs,
field.Invalid(field.NewPath("spec", "compartmentId"), c.Spec.CompartmentId, "field is invalid"))
}

if len(allErrs) == 0 {
return nil
}

return allErrs
}
124 changes: 124 additions & 0 deletions api/v1beta1/ocicluster_webhook_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
*
* Copyright (c) 2022, Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* /
*
*/

package v1beta1

import (
"testing"

"github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestOCICluster_ValidateCreate(t *testing.T) {

tests := []struct {
name string
c *OCICluster
expectErr bool
}{
{
name: "shouldn't allow bad ImageId",
c: &OCICluster{
ObjectMeta: metav1.ObjectMeta{},
Spec: OCIClusterSpec{
CompartmentId: "badocid",
},
},
expectErr: true,
},
{
name: "should succeed",
c: &OCICluster{
ObjectMeta: metav1.ObjectMeta{},
Spec: OCIClusterSpec{
CompartmentId: "ocid",
},
},
expectErr: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
g := gomega.NewWithT(t)

if test.expectErr {
g.Expect(test.c.ValidateCreate()).NotTo(gomega.Succeed())
} else {
g.Expect(test.c.ValidateCreate()).To(gomega.Succeed())
}
})
}
}

func TestOCICluster_ValidateUpdate(t *testing.T) {
tests := []struct {
name string
c *OCICluster
old *OCICluster
expectErr bool
}{
{
name: "shouldn't region change",
c: &OCICluster{
ObjectMeta: metav1.ObjectMeta{},
Spec: OCIClusterSpec{
Region: "new-region",
},
},
old: &OCICluster{
ObjectMeta: metav1.ObjectMeta{},
Spec: OCIClusterSpec{
Region: "old-region",
},
},
expectErr: true,
},
{
name: "should succeed",
c: &OCICluster{
ObjectMeta: metav1.ObjectMeta{},
Spec: OCIClusterSpec{
CompartmentId: "ocid",
Region: "old-region",
},
},
old: &OCICluster{
ObjectMeta: metav1.ObjectMeta{},
Spec: OCIClusterSpec{
Region: "old-region",
},
},
expectErr: false,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
g := gomega.NewWithT(t)

if test.expectErr {
g.Expect(test.c.ValidateUpdate(test.old)).NotTo(gomega.Succeed())
} else {
g.Expect(test.c.ValidateUpdate(test.old)).To(gomega.Succeed())
}
})
}

}
108 changes: 108 additions & 0 deletions api/v1beta1/ocimachinetemplate_webhook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
*
* Copyright (c) 2022, Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* /
*
*/

package v1beta1

import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/webhook"
)

var (
_ webhook.Validator = &OCIMachineTemplate{}
)

// +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

func (m *OCIMachineTemplate) SetupWebhookWithManager(mgr ctrl.Manager) error {
return ctrl.NewWebhookManagedBy(mgr).
For(m).
Complete()
}

// ValidateCreate implements webhook.Validator so a webhook will be registered for the type.
func (m *OCIMachineTemplate) ValidateCreate() error {
clusterlogger.Info("validate create machinetemplate", "name", m.Name)

var allErrs field.ErrorList

allErrs = append(allErrs, m.validate()...)

if len(allErrs) == 0 {
return nil
}

return apierrors.NewInvalid(m.GroupVersionKind().GroupKind(), m.Name, allErrs)
}

// ValidateDelete implements webhook.Validator so a webhook will be registered for the type.
func (m *OCIMachineTemplate) ValidateDelete() error {
clusterlogger.Info("validate delete machinetemplate", "name", m.Name)

return nil
}

// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type.
func (m *OCIMachineTemplate) ValidateUpdate(old runtime.Object) error {
clusterlogger.Info("validate update machinetemplate", "name", m.Name)

var allErrs field.ErrorList

allErrs = append(allErrs, m.validate()...)

if len(allErrs) == 0 {
return nil
}

return apierrors.NewInvalid(m.GroupVersionKind().GroupKind(), m.Name, allErrs)
}

func (m *OCIMachineTemplate) validate() field.ErrorList {
var allErrs field.ErrorList

if len(m.Spec.Template.Spec.ImageId) > 0 && !validOcid(m.Spec.Template.Spec.ImageId) {
allErrs = append(
allErrs,
field.Invalid(
field.NewPath("Spec", "Template", "Spec", "imageId"),
m.Spec.Template.Spec.ImageId,
"field is invalid"),
)
}

// simple validity test for compartment
if len(m.Spec.Template.Spec.CompartmentId) > 0 && !validOcid(m.Spec.Template.Spec.CompartmentId) {
allErrs = append(
allErrs,
field.Invalid(
field.NewPath("Spec", "Template", "Spec", "compartmentId"),
m.Spec.Template.Spec.CompartmentId,
"field is invalid"),
)
}

if len(allErrs) == 0 {
return nil
}

return allErrs
}
Loading

0 comments on commit f459f94

Please sign in to comment.