Skip to content

Commit ef6fd50

Browse files
committed
Add validation and defaulting
1 parent 43d2eee commit ef6fd50

File tree

1 file changed

+151
-6
lines changed

1 file changed

+151
-6
lines changed

api/v1beta1/hetznercluster_webhook.go

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

1919
import (
20+
"context"
2021
"fmt"
22+
"net"
2123
"reflect"
2224

2325
apierrors "k8s.io/apimachinery/pkg/api/errors"
2426
"k8s.io/apimachinery/pkg/runtime"
2527
"k8s.io/apimachinery/pkg/util/validation/field"
28+
"k8s.io/utils/ptr"
2629
ctrl "sigs.k8s.io/controller-runtime"
2730
"sigs.k8s.io/controller-runtime/pkg/webhook"
2831
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
@@ -42,10 +45,22 @@ var regionNetworkZoneMap = map[string]string{
4245
"sin": "ap-southeast",
4346
}
4447

48+
const (
49+
// DefaultCIDRBlock specifies the default CIDR block used by the HCloudNetwork.
50+
DefaultCIDRBlock = "10.0.0.0/16"
51+
52+
// DefaultSubnetCIDRBlock specifies the default subnet CIDR block used by the HCloudNetwork.
53+
DefaultSubnetCIDRBlock = "10.0.0.0/24"
54+
55+
// DefaultNetworkZone specifies the default network zone used by the HCloudNetwork.
56+
DefaultNetworkZone = "eu-central"
57+
)
58+
4559
// SetupWebhookWithManager initializes webhook manager for HetznerCluster.
4660
func (r *HetznerCluster) SetupWebhookWithManager(mgr ctrl.Manager) error {
4761
return ctrl.NewWebhookManagedBy(mgr).
4862
For(r).
63+
WithDefaulter(r).
4964
Complete()
5065
}
5166

@@ -60,10 +75,35 @@ func (r *HetznerClusterList) SetupWebhookWithManager(mgr ctrl.Manager) error {
6075

6176
//+kubebuilder:webhook:path=/mutate-infrastructure-cluster-x-k8s-io-v1beta1-hetznercluster,mutating=true,failurePolicy=fail,sideEffects=None,groups=infrastructure.cluster.x-k8s.io,resources=hetznerclusters,verbs=create;update,versions=v1beta1,name=mutation.hetznercluster.infrastructure.cluster.x-k8s.io,admissionReviewVersions={v1,v1beta1}
6277

63-
var _ webhook.Defaulter = &HetznerCluster{}
78+
var _ webhook.CustomDefaulter = &HetznerCluster{}
6479

65-
// Default implements webhook.Defaulter so a webhook will be registered for the type.
66-
func (r *HetznerCluster) Default() {}
80+
// Default implements webhook.CustomDefaulter so a webhook will be registered for the type.
81+
func (r *HetznerCluster) Default(_ context.Context, obj runtime.Object) error {
82+
hetznerclusterlog.V(1).Info("default", "name", r.Name)
83+
84+
cluster, ok := obj.(*HetznerCluster)
85+
if !ok {
86+
return apierrors.NewBadRequest(fmt.Sprintf("expected an HetznerCluster but got a %T", obj))
87+
}
88+
89+
if cluster.Spec.HCloudNetwork.Enabled {
90+
if cluster.Spec.HCloudNetwork.ID != nil {
91+
return nil
92+
}
93+
94+
if cluster.Spec.HCloudNetwork.CIDRBlock == nil {
95+
cluster.Spec.HCloudNetwork.CIDRBlock = ptr.To(DefaultCIDRBlock)
96+
}
97+
if cluster.Spec.HCloudNetwork.SubnetCIDRBlock == nil {
98+
cluster.Spec.HCloudNetwork.SubnetCIDRBlock = ptr.To(DefaultSubnetCIDRBlock)
99+
}
100+
if cluster.Spec.HCloudNetwork.NetworkZone == nil {
101+
cluster.Spec.HCloudNetwork.NetworkZone = ptr.To[HCloudNetworkZone](DefaultNetworkZone)
102+
}
103+
}
104+
105+
return nil
106+
}
67107

68108
//+kubebuilder:webhook:path=/validate-infrastructure-cluster-x-k8s-io-v1beta1-hetznercluster,mutating=false,failurePolicy=fail,sideEffects=None,groups=infrastructure.cluster.x-k8s.io,resources=hetznerclusters,verbs=create;update,versions=v1beta1,name=validation.hetznercluster.infrastructure.cluster.x-k8s.io,admissionReviewVersions={v1,v1beta1}
69109

@@ -109,6 +149,55 @@ func (r *HetznerCluster) ValidateCreate() (admission.Warnings, error) {
109149
if err := isNetworkZoneSameForAllRegions(r.Spec.ControlPlaneRegions, nil); err != nil {
110150
allErrs = append(allErrs, err)
111151
}
152+
} else {
153+
// If ID is given check that all other network settings are empty.
154+
if r.Spec.HCloudNetwork.ID != nil {
155+
if errs := areCIDRsAndNetworkZoneEmpty(r.Spec.HCloudNetwork); errs != nil {
156+
allErrs = append(allErrs, errs...)
157+
}
158+
} else {
159+
// If no ID is given check the other network settings for valid entries.
160+
if r.Spec.HCloudNetwork.NetworkZone != nil {
161+
givenZone := string(*r.Spec.HCloudNetwork.NetworkZone)
162+
163+
var validNetworkZone bool
164+
for _, z := range regionNetworkZoneMap {
165+
if givenZone == z {
166+
validNetworkZone = true
167+
break
168+
}
169+
}
170+
if !validNetworkZone {
171+
allErrs = append(allErrs, field.Invalid(
172+
field.NewPath("spec", "hcloudNetwork", "networkZone"),
173+
r.Spec.HCloudNetwork.NetworkZone,
174+
"wrong network zone. Should be eu-central, us-east, us-west or ap-southeast"),
175+
)
176+
}
177+
}
178+
179+
if r.Spec.HCloudNetwork.CIDRBlock != nil {
180+
_, _, err := net.ParseCIDR(*r.Spec.HCloudNetwork.CIDRBlock)
181+
if err != nil {
182+
allErrs = append(allErrs, field.Invalid(
183+
field.NewPath("spec", "hcloudNetwork", "cidrBlock"),
184+
r.Spec.HCloudNetwork.CIDRBlock,
185+
"malformed cidrBlock"),
186+
)
187+
}
188+
}
189+
190+
if r.Spec.HCloudNetwork.SubnetCIDRBlock != nil {
191+
_, _, err := net.ParseCIDR(*r.Spec.HCloudNetwork.SubnetCIDRBlock)
192+
if err != nil {
193+
allErrs = append(allErrs, field.Invalid(
194+
field.NewPath("spec", "hcloudNetwork", "subnetCIDRBlock"),
195+
r.Spec.HCloudNetwork.SubnetCIDRBlock,
196+
"malformed cidrBlock"),
197+
)
198+
}
199+
}
200+
}
112201
}
113202

114203
// Check whether controlPlaneEndpoint is specified if allow empty is not set or false
@@ -161,13 +250,46 @@ func (r *HetznerCluster) ValidateUpdate(old runtime.Object) (admission.Warnings,
161250
return nil, apierrors.NewBadRequest(fmt.Sprintf("expected an HetznerCluster but got a %T", old))
162251
}
163252

164-
// Network settings are immutable
165-
if !reflect.DeepEqual(oldC.Spec.HCloudNetwork, r.Spec.HCloudNetwork) {
253+
if !reflect.DeepEqual(oldC.Spec.HCloudNetwork.Enabled, r.Spec.HCloudNetwork.Enabled) {
166254
allErrs = append(allErrs,
167-
field.Invalid(field.NewPath("spec", "hcloudNetwork"), r.Spec.HCloudNetwork, "field is immutable"),
255+
field.Invalid(field.NewPath("spec", "hcloudNetwork", "enabled"), r.Spec.HCloudNetwork.Enabled, "field is immutable"),
168256
)
169257
}
170258

259+
if oldC.Spec.HCloudNetwork.Enabled {
260+
// Only allow updating the network ID when it was not set previously. This makes it possible to e.g. adopt the
261+
// network that was created initially by CAPH.
262+
if oldC.Spec.HCloudNetwork.ID != nil && !reflect.DeepEqual(oldC.Spec.HCloudNetwork.ID, r.Spec.HCloudNetwork.ID) {
263+
allErrs = append(allErrs,
264+
field.Invalid(field.NewPath("spec", "hcloudNetwork", "id"), r.Spec.HCloudNetwork.ID, "field is immutable"),
265+
)
266+
}
267+
268+
if r.Spec.HCloudNetwork.ID != nil {
269+
if errs := areCIDRsAndNetworkZoneEmpty(r.Spec.HCloudNetwork); errs != nil {
270+
allErrs = append(allErrs, errs...)
271+
}
272+
} else {
273+
if !reflect.DeepEqual(oldC.Spec.HCloudNetwork.CIDRBlock, r.Spec.HCloudNetwork.CIDRBlock) {
274+
allErrs = append(allErrs,
275+
field.Invalid(field.NewPath("spec", "hcloudNetwork", "cidrBlock"), r.Spec.HCloudNetwork.CIDRBlock, "field is immutable"),
276+
)
277+
}
278+
279+
if !reflect.DeepEqual(oldC.Spec.HCloudNetwork.SubnetCIDRBlock, r.Spec.HCloudNetwork.SubnetCIDRBlock) {
280+
allErrs = append(allErrs,
281+
field.Invalid(field.NewPath("spec", "hcloudNetwork", "subnetCIDRBlock"), r.Spec.HCloudNetwork.SubnetCIDRBlock, "field is immutable"),
282+
)
283+
}
284+
285+
if !reflect.DeepEqual(oldC.Spec.HCloudNetwork.NetworkZone, r.Spec.HCloudNetwork.NetworkZone) {
286+
allErrs = append(allErrs,
287+
field.Invalid(field.NewPath("spec", "hcloudNetwork", "networkZone"), r.Spec.HCloudNetwork.NetworkZone, "field is immutable"),
288+
)
289+
}
290+
}
291+
}
292+
171293
// Check if all regions are in the same network zone if a private network is enabled
172294
if oldC.Spec.HCloudNetwork.Enabled {
173295
var defaultNetworkZone *string
@@ -225,3 +347,26 @@ func (r *HetznerCluster) ValidateDelete() (admission.Warnings, error) {
225347
hetznerclusterlog.V(1).Info("validate delete", "name", r.Name)
226348
return nil, nil
227349
}
350+
351+
func areCIDRsAndNetworkZoneEmpty(hcloudNetwork HCloudNetworkSpec) field.ErrorList {
352+
var allErrs field.ErrorList
353+
if hcloudNetwork.CIDRBlock != nil {
354+
allErrs = append(allErrs,
355+
field.Invalid(field.NewPath("spec", "hcloudNetwork", "cidrBlock"), hcloudNetwork.CIDRBlock, "field must be empty"),
356+
)
357+
}
358+
359+
if hcloudNetwork.SubnetCIDRBlock != nil {
360+
allErrs = append(allErrs,
361+
field.Invalid(field.NewPath("spec", "hcloudNetwork", "subnetCIDRBlock"), hcloudNetwork.SubnetCIDRBlock, "field must be empty"),
362+
)
363+
}
364+
365+
if hcloudNetwork.NetworkZone != nil {
366+
allErrs = append(allErrs,
367+
field.Invalid(field.NewPath("spec", "hcloudNetwork", "networkZone"), hcloudNetwork.NetworkZone, "field must be empty"),
368+
)
369+
}
370+
371+
return allErrs
372+
}

0 commit comments

Comments
 (0)