Skip to content

Commit 4c9281b

Browse files
authored
feat: linodecluster: add validating admission webhook on create (#320)
* feat: linodecluster: kubebuilder create webhook Scaffold a validating admission webhook for the LinodeCluster resource with Kubebuider via the command: kubebuilder create webhook --group infrastructure --version v1alpha1 --kind LinodeCluster --programmatic-validation * fixup! feat: linodecluster: kubebuilder create webhook * api: linodecluster: add create validation * fixup! add spec for LinodeCluster
1 parent 1a93e85 commit 4c9281b

10 files changed

+208
-2
lines changed

PROJECT

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ resources:
1717
kind: LinodeCluster
1818
path: github.com/linode/cluster-api-provider-linode/api/v1alpha1
1919
version: v1alpha1
20+
webhooks:
21+
validation: true
22+
webhookVersion: v1
2023
- api:
2124
crdVersion: v1
2225
namespaced: true

api/v1alpha1/linodecluster_types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
// LinodeClusterSpec defines the desired state of LinodeCluster
2727
type LinodeClusterSpec struct {
2828
// The Linode Region the LinodeCluster lives in.
29+
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable"
2930
Region string `json:"region"`
3031

3132
// ControlPlaneEndpoint represents the endpoint used to communicate with the LinodeCluster control plane.

api/v1alpha1/linodecluster_webhook.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
Copyright 2023 Akamai Technologies, Inc.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1alpha1
18+
19+
import (
20+
"context"
21+
"slices"
22+
23+
apierrors "k8s.io/apimachinery/pkg/api/errors"
24+
"k8s.io/apimachinery/pkg/runtime"
25+
"k8s.io/apimachinery/pkg/runtime/schema"
26+
"k8s.io/apimachinery/pkg/util/validation/field"
27+
ctrl "sigs.k8s.io/controller-runtime"
28+
logf "sigs.k8s.io/controller-runtime/pkg/log"
29+
"sigs.k8s.io/controller-runtime/pkg/webhook"
30+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
31+
32+
. "github.com/linode/cluster-api-provider-linode/clients"
33+
)
34+
35+
// log is for logging in this package.
36+
var linodeclusterlog = logf.Log.WithName("linodecluster-resource")
37+
38+
// SetupWebhookWithManager will setup the manager to manage the webhooks
39+
func (r *LinodeCluster) SetupWebhookWithManager(mgr ctrl.Manager) error {
40+
return ctrl.NewWebhookManagedBy(mgr).
41+
For(r).
42+
Complete()
43+
}
44+
45+
// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable updation and deletion validation.
46+
//+kubebuilder:webhook:path=/validate-infrastructure-cluster-x-k8s-io-v1alpha1-linodecluster,mutating=false,failurePolicy=fail,sideEffects=None,groups=infrastructure.cluster.x-k8s.io,resources=linodeclusters,verbs=create,versions=v1alpha1,name=vlinodecluster.kb.io,admissionReviewVersions=v1
47+
48+
var _ webhook.Validator = &LinodeCluster{}
49+
50+
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
51+
func (r *LinodeCluster) ValidateCreate() (admission.Warnings, error) {
52+
linodeclusterlog.Info("validate create", "name", r.Name)
53+
54+
ctx, cancel := context.WithTimeout(context.Background(), defaultWebhookTimeout)
55+
defer cancel()
56+
57+
return nil, r.validateLinodeCluster(ctx, &defaultLinodeClient)
58+
}
59+
60+
// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
61+
func (r *LinodeCluster) ValidateUpdate(old runtime.Object) (admission.Warnings, error) {
62+
linodeclusterlog.Info("validate update", "name", r.Name)
63+
64+
// TODO(user): fill in your validation logic upon object update.
65+
return nil, nil
66+
}
67+
68+
// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
69+
func (r *LinodeCluster) ValidateDelete() (admission.Warnings, error) {
70+
linodeclusterlog.Info("validate delete", "name", r.Name)
71+
72+
// TODO(user): fill in your validation logic upon object deletion.
73+
return nil, nil
74+
}
75+
76+
func (r *LinodeCluster) validateLinodeCluster(ctx context.Context, client LinodeClient) error {
77+
var errs field.ErrorList
78+
79+
if err := r.validateLinodeClusterSpec(ctx, client); err != nil {
80+
errs = slices.Concat(errs, err)
81+
}
82+
83+
if len(errs) == 0 {
84+
return nil
85+
}
86+
return apierrors.NewInvalid(
87+
schema.GroupKind{Group: "infrastructure.cluster.x-k8s.io", Kind: "LinodeCluster"},
88+
r.Name, errs)
89+
}
90+
91+
func (r *LinodeCluster) validateLinodeClusterSpec(ctx context.Context, client LinodeClient) field.ErrorList {
92+
var errs field.ErrorList
93+
94+
if err := validateRegion(ctx, client, r.Spec.Region, field.NewPath("spec").Child("region")); err != nil {
95+
errs = append(errs, err)
96+
}
97+
98+
if len(errs) == 0 {
99+
return nil
100+
}
101+
return errs
102+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
Copyright 2023 Akamai Technologies, Inc.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1alpha1
18+
19+
import (
20+
"context"
21+
"errors"
22+
"testing"
23+
24+
"github.com/stretchr/testify/assert"
25+
"go.uber.org/mock/gomock"
26+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
28+
"github.com/linode/cluster-api-provider-linode/mock"
29+
30+
. "github.com/linode/cluster-api-provider-linode/mock/mocktest"
31+
)
32+
33+
func TestValidateLinodeCluster(t *testing.T) {
34+
t.Parallel()
35+
36+
var (
37+
cluster = LinodeCluster{
38+
ObjectMeta: metav1.ObjectMeta{
39+
Name: "example",
40+
Namespace: "example",
41+
},
42+
Spec: LinodeClusterSpec{
43+
Region: "example",
44+
},
45+
}
46+
)
47+
48+
NewSuite(t, mock.MockLinodeClient{}).Run(
49+
OneOf(
50+
Path(
51+
Call("valid", func(ctx context.Context, mck Mock) {
52+
mck.LinodeClient.EXPECT().GetRegion(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
53+
}),
54+
Result("success", func(ctx context.Context, mck Mock) {
55+
assert.NoError(t, cluster.validateLinodeCluster(ctx, mck.LinodeClient))
56+
}),
57+
),
58+
),
59+
OneOf(
60+
Path(Call("invalid region", func(ctx context.Context, mck Mock) {
61+
mck.LinodeClient.EXPECT().GetRegion(gomock.Any(), gomock.Any()).Return(nil, errors.New("invalid region")).AnyTimes()
62+
})),
63+
),
64+
Result("error", func(ctx context.Context, mck Mock) {
65+
assert.Error(t, cluster.validateLinodeCluster(ctx, mck.LinodeClient))
66+
}),
67+
)
68+
}

api/v1alpha1/webhook_suite_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ var _ = BeforeSuite(func() {
114114
})
115115
Expect(err).NotTo(HaveOccurred())
116116

117+
err = (&LinodeCluster{}).SetupWebhookWithManager(mgr)
118+
Expect(err).NotTo(HaveOccurred())
119+
117120
err = (&LinodeMachine{}).SetupWebhookWithManager(mgr)
118121
Expect(err).NotTo(HaveOccurred())
119122

cmd/main.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@ func main() {
150150
os.Exit(1)
151151
}
152152
if os.Getenv("ENABLE_WEBHOOKS") != "false" {
153+
if err = (&infrastructurev1alpha1.LinodeCluster{}).SetupWebhookWithManager(mgr); err != nil {
154+
setupLog.Error(err, "unable to create webhook", "webhook", "LinodeCluster")
155+
os.Exit(1)
156+
}
153157
if err = (&infrastructurev1alpha1.LinodeMachine{}).SetupWebhookWithManager(mgr); err != nil {
154158
setupLog.Error(err, "unable to create webhook", "webhook", "LinodeMachine")
155159
os.Exit(1)

config/crd/bases/infrastructure.cluster.x-k8s.io_linodeclusters.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,9 @@ spec:
116116
region:
117117
description: The Linode Region the LinodeCluster lives in.
118118
type: string
119+
x-kubernetes-validations:
120+
- message: Value is immutable
121+
rule: self == oldSelf
119122
vpcRef:
120123
description: |-
121124
ObjectReference contains enough information to let you inspect or modify the referred object.

config/crd/bases/infrastructure.cluster.x-k8s.io_linodeclustertemplates.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ spec:
110110
region:
111111
description: The Linode Region the LinodeCluster lives in.
112112
type: string
113+
x-kubernetes-validations:
114+
- message: Value is immutable
115+
rule: self == oldSelf
113116
vpcRef:
114117
description: |-
115118
ObjectReference contains enough information to let you inspect or modify the referred object.

config/crd/kustomization.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ resources:
2121
patches:
2222
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.
2323
# patches here are for enabling the conversion webhook for each CRD
24-
#- path: patches/webhook_in_linodeclusters.yaml
24+
- path: patches/webhook_in_linodeclusters.yaml
2525
- path: patches/webhook_in_linodemachines.yaml
2626
#- path: patches/webhook_in_linodemachinetemplates.yaml
2727
#- path: patches/webhook_in_linodeclustertemplates.yaml
@@ -30,7 +30,7 @@ patches:
3030

3131
# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix.
3232
# patches here are for enabling the CA injection for each CRD
33-
#- path: patches/cainjection_in_linodeclusters.yaml
33+
- path: patches/cainjection_in_linodeclusters.yaml
3434
- path: patches/cainjection_in_linodemachines.yaml
3535
#- path: patches/cainjection_in_linodemachinetemplates.yaml
3636
#- path: patches/cainjection_in_linodeclustertemplates.yaml

config/webhook/manifests.yaml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,25 @@ kind: ValidatingWebhookConfiguration
44
metadata:
55
name: validating-webhook-configuration
66
webhooks:
7+
- admissionReviewVersions:
8+
- v1
9+
clientConfig:
10+
service:
11+
name: webhook-service
12+
namespace: system
13+
path: /validate-infrastructure-cluster-x-k8s-io-v1alpha1-linodecluster
14+
failurePolicy: Fail
15+
name: vlinodecluster.kb.io
16+
rules:
17+
- apiGroups:
18+
- infrastructure.cluster.x-k8s.io
19+
apiVersions:
20+
- v1alpha1
21+
operations:
22+
- CREATE
23+
resources:
24+
- linodeclusters
25+
sideEffects: None
726
- admissionReviewVersions:
827
- v1
928
clientConfig:

0 commit comments

Comments
 (0)