@@ -17,12 +17,15 @@ limitations under the License.
17
17
package v1beta1
18
18
19
19
import (
20
+ "context"
20
21
"fmt"
22
+ "net"
21
23
"reflect"
22
24
23
25
apierrors "k8s.io/apimachinery/pkg/api/errors"
24
26
"k8s.io/apimachinery/pkg/runtime"
25
27
"k8s.io/apimachinery/pkg/util/validation/field"
28
+ "k8s.io/utils/ptr"
26
29
ctrl "sigs.k8s.io/controller-runtime"
27
30
"sigs.k8s.io/controller-runtime/pkg/webhook"
28
31
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
@@ -42,10 +45,22 @@ var regionNetworkZoneMap = map[string]string{
42
45
"sin" : "ap-southeast" ,
43
46
}
44
47
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
+
45
59
// SetupWebhookWithManager initializes webhook manager for HetznerCluster.
46
60
func (r * HetznerCluster ) SetupWebhookWithManager (mgr ctrl.Manager ) error {
47
61
return ctrl .NewWebhookManagedBy (mgr ).
48
62
For (r ).
63
+ WithDefaulter (r ).
49
64
Complete ()
50
65
}
51
66
@@ -60,10 +75,35 @@ func (r *HetznerClusterList) SetupWebhookWithManager(mgr ctrl.Manager) error {
60
75
61
76
//+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}
62
77
63
- var _ webhook.Defaulter = & HetznerCluster {}
78
+ var _ webhook.CustomDefaulter = & HetznerCluster {}
64
79
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
+ }
67
107
68
108
//+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}
69
109
@@ -109,6 +149,55 @@ func (r *HetznerCluster) ValidateCreate() (admission.Warnings, error) {
109
149
if err := isNetworkZoneSameForAllRegions (r .Spec .ControlPlaneRegions , nil ); err != nil {
110
150
allErrs = append (allErrs , err )
111
151
}
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
+ }
112
201
}
113
202
114
203
// 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,
161
250
return nil , apierrors .NewBadRequest (fmt .Sprintf ("expected an HetznerCluster but got a %T" , old ))
162
251
}
163
252
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 ) {
166
254
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" ),
168
256
)
169
257
}
170
258
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
+
171
293
// Check if all regions are in the same network zone if a private network is enabled
172
294
if oldC .Spec .HCloudNetwork .Enabled {
173
295
var defaultNetworkZone * string
@@ -225,3 +347,26 @@ func (r *HetznerCluster) ValidateDelete() (admission.Warnings, error) {
225
347
hetznerclusterlog .V (1 ).Info ("validate delete" , "name" , r .Name )
226
348
return nil , nil
227
349
}
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