sigs.k8s.io/cluster-api@v1.7.1/bootstrap/kubeadm/internal/webhooks/kubeadmconfig_test.go (about) 1 /* 2 Copyright 2021 The Kubernetes Authors. 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 webhooks 18 19 import ( 20 "testing" 21 22 . "github.com/onsi/gomega" 23 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 utilfeature "k8s.io/component-base/featuregate/testing" 25 "k8s.io/utils/ptr" 26 ctrl "sigs.k8s.io/controller-runtime" 27 28 bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1" 29 "sigs.k8s.io/cluster-api/feature" 30 "sigs.k8s.io/cluster-api/internal/webhooks/util" 31 ) 32 33 var ctx = ctrl.SetupSignalHandler() 34 35 func TestKubeadmConfigDefault(t *testing.T) { 36 defer utilfeature.SetFeatureGateDuringTest(t, feature.Gates, feature.ClusterTopology, true)() 37 38 g := NewWithT(t) 39 40 kubeadmConfig := &bootstrapv1.KubeadmConfig{ 41 ObjectMeta: metav1.ObjectMeta{ 42 Namespace: "foo", 43 }, 44 Spec: bootstrapv1.KubeadmConfigSpec{}, 45 } 46 updateDefaultingKubeadmConfig := kubeadmConfig.DeepCopy() 47 updateDefaultingKubeadmConfig.Spec.Verbosity = ptr.To[int32](4) 48 webhook := &KubeadmConfig{} 49 t.Run("for KubeadmConfig", util.CustomDefaultValidateTest(ctx, updateDefaultingKubeadmConfig, webhook)) 50 51 g.Expect(webhook.Default(ctx, kubeadmConfig)).To(Succeed()) 52 53 g.Expect(kubeadmConfig.Spec.Format).To(Equal(bootstrapv1.CloudConfig)) 54 55 ignitionKubeadmConfig := &bootstrapv1.KubeadmConfig{ 56 ObjectMeta: metav1.ObjectMeta{ 57 Namespace: "foo", 58 }, 59 Spec: bootstrapv1.KubeadmConfigSpec{ 60 Format: bootstrapv1.Ignition, 61 }, 62 } 63 g.Expect(webhook.Default(ctx, ignitionKubeadmConfig)).To(Succeed()) 64 g.Expect(ignitionKubeadmConfig.Spec.Format).To(Equal(bootstrapv1.Ignition)) 65 } 66 67 func TestKubeadmConfigValidate(t *testing.T) { 68 cases := map[string]struct { 69 in *bootstrapv1.KubeadmConfig 70 enableIgnitionFeature bool 71 expectErr bool 72 }{ 73 "valid content": { 74 in: &bootstrapv1.KubeadmConfig{ 75 ObjectMeta: metav1.ObjectMeta{ 76 Name: "baz", 77 Namespace: metav1.NamespaceDefault, 78 }, 79 Spec: bootstrapv1.KubeadmConfigSpec{ 80 Files: []bootstrapv1.File{ 81 { 82 Content: "foo", 83 }, 84 }, 85 }, 86 }, 87 }, 88 "valid contentFrom": { 89 in: &bootstrapv1.KubeadmConfig{ 90 ObjectMeta: metav1.ObjectMeta{ 91 Name: "baz", 92 Namespace: metav1.NamespaceDefault, 93 }, 94 Spec: bootstrapv1.KubeadmConfigSpec{ 95 Files: []bootstrapv1.File{ 96 { 97 ContentFrom: &bootstrapv1.FileSource{ 98 Secret: bootstrapv1.SecretFileSource{ 99 Name: "foo", 100 Key: "bar", 101 }, 102 }, 103 }, 104 }, 105 }, 106 }, 107 }, 108 "invalid content and contentFrom": { 109 in: &bootstrapv1.KubeadmConfig{ 110 ObjectMeta: metav1.ObjectMeta{ 111 Name: "baz", 112 Namespace: metav1.NamespaceDefault, 113 }, 114 Spec: bootstrapv1.KubeadmConfigSpec{ 115 Files: []bootstrapv1.File{ 116 { 117 ContentFrom: &bootstrapv1.FileSource{}, 118 Content: "foo", 119 }, 120 }, 121 }, 122 }, 123 expectErr: true, 124 }, 125 "invalid contentFrom without name": { 126 in: &bootstrapv1.KubeadmConfig{ 127 ObjectMeta: metav1.ObjectMeta{ 128 Name: "baz", 129 Namespace: metav1.NamespaceDefault, 130 }, 131 Spec: bootstrapv1.KubeadmConfigSpec{ 132 Files: []bootstrapv1.File{ 133 { 134 ContentFrom: &bootstrapv1.FileSource{ 135 Secret: bootstrapv1.SecretFileSource{ 136 Key: "bar", 137 }, 138 }, 139 Content: "foo", 140 }, 141 }, 142 }, 143 }, 144 expectErr: true, 145 }, 146 "invalid contentFrom without key": { 147 in: &bootstrapv1.KubeadmConfig{ 148 ObjectMeta: metav1.ObjectMeta{ 149 Name: "baz", 150 Namespace: metav1.NamespaceDefault, 151 }, 152 Spec: bootstrapv1.KubeadmConfigSpec{ 153 Files: []bootstrapv1.File{ 154 { 155 ContentFrom: &bootstrapv1.FileSource{ 156 Secret: bootstrapv1.SecretFileSource{ 157 Name: "foo", 158 }, 159 }, 160 Content: "foo", 161 }, 162 }, 163 }, 164 }, 165 expectErr: true, 166 }, 167 "invalid with duplicate file path": { 168 in: &bootstrapv1.KubeadmConfig{ 169 ObjectMeta: metav1.ObjectMeta{ 170 Name: "baz", 171 Namespace: metav1.NamespaceDefault, 172 }, 173 Spec: bootstrapv1.KubeadmConfigSpec{ 174 Files: []bootstrapv1.File{ 175 { 176 Content: "foo", 177 }, 178 { 179 Content: "bar", 180 }, 181 }, 182 }, 183 }, 184 expectErr: true, 185 }, 186 "valid passwd": { 187 in: &bootstrapv1.KubeadmConfig{ 188 ObjectMeta: metav1.ObjectMeta{ 189 Name: "baz", 190 Namespace: metav1.NamespaceDefault, 191 }, 192 Spec: bootstrapv1.KubeadmConfigSpec{ 193 Users: []bootstrapv1.User{ 194 { 195 Passwd: ptr.To("foo"), 196 }, 197 }, 198 }, 199 }, 200 }, 201 "valid passwdFrom": { 202 in: &bootstrapv1.KubeadmConfig{ 203 ObjectMeta: metav1.ObjectMeta{ 204 Name: "baz", 205 Namespace: metav1.NamespaceDefault, 206 }, 207 Spec: bootstrapv1.KubeadmConfigSpec{ 208 Users: []bootstrapv1.User{ 209 { 210 PasswdFrom: &bootstrapv1.PasswdSource{ 211 Secret: bootstrapv1.SecretPasswdSource{ 212 Name: "foo", 213 Key: "bar", 214 }, 215 }, 216 }, 217 }, 218 }, 219 }, 220 }, 221 "invalid passwd and passwdFrom": { 222 in: &bootstrapv1.KubeadmConfig{ 223 ObjectMeta: metav1.ObjectMeta{ 224 Name: "baz", 225 Namespace: metav1.NamespaceDefault, 226 }, 227 Spec: bootstrapv1.KubeadmConfigSpec{ 228 Users: []bootstrapv1.User{ 229 { 230 PasswdFrom: &bootstrapv1.PasswdSource{}, 231 Passwd: ptr.To("foo"), 232 }, 233 }, 234 }, 235 }, 236 expectErr: true, 237 }, 238 "invalid passwdFrom without name": { 239 in: &bootstrapv1.KubeadmConfig{ 240 ObjectMeta: metav1.ObjectMeta{ 241 Name: "baz", 242 Namespace: metav1.NamespaceDefault, 243 }, 244 Spec: bootstrapv1.KubeadmConfigSpec{ 245 Users: []bootstrapv1.User{ 246 { 247 PasswdFrom: &bootstrapv1.PasswdSource{ 248 Secret: bootstrapv1.SecretPasswdSource{ 249 Key: "bar", 250 }, 251 }, 252 Passwd: ptr.To("foo"), 253 }, 254 }, 255 }, 256 }, 257 expectErr: true, 258 }, 259 "invalid passwdFrom without key": { 260 in: &bootstrapv1.KubeadmConfig{ 261 ObjectMeta: metav1.ObjectMeta{ 262 Name: "baz", 263 Namespace: metav1.NamespaceDefault, 264 }, 265 Spec: bootstrapv1.KubeadmConfigSpec{ 266 Users: []bootstrapv1.User{ 267 { 268 PasswdFrom: &bootstrapv1.PasswdSource{ 269 Secret: bootstrapv1.SecretPasswdSource{ 270 Name: "foo", 271 }, 272 }, 273 Passwd: ptr.To("foo"), 274 }, 275 }, 276 }, 277 }, 278 expectErr: true, 279 }, 280 "Ignition field is set, format is not Ignition": { 281 enableIgnitionFeature: true, 282 in: &bootstrapv1.KubeadmConfig{ 283 ObjectMeta: metav1.ObjectMeta{ 284 Name: "baz", 285 Namespace: "default", 286 }, 287 Spec: bootstrapv1.KubeadmConfigSpec{ 288 Ignition: &bootstrapv1.IgnitionSpec{}, 289 }, 290 }, 291 expectErr: true, 292 }, 293 "Ignition field is not set, format is Ignition": { 294 enableIgnitionFeature: true, 295 in: &bootstrapv1.KubeadmConfig{ 296 ObjectMeta: metav1.ObjectMeta{ 297 Name: "baz", 298 Namespace: "default", 299 }, 300 Spec: bootstrapv1.KubeadmConfigSpec{ 301 Format: bootstrapv1.Ignition, 302 }, 303 }, 304 }, 305 "format is Ignition, user is inactive": { 306 enableIgnitionFeature: true, 307 in: &bootstrapv1.KubeadmConfig{ 308 ObjectMeta: metav1.ObjectMeta{ 309 Name: "baz", 310 Namespace: "default", 311 }, 312 Spec: bootstrapv1.KubeadmConfigSpec{ 313 Format: bootstrapv1.Ignition, 314 Users: []bootstrapv1.User{ 315 { 316 Inactive: ptr.To(true), 317 }, 318 }, 319 }, 320 }, 321 expectErr: true, 322 }, 323 "format is Ignition, non-GPT partition configured": { 324 enableIgnitionFeature: true, 325 in: &bootstrapv1.KubeadmConfig{ 326 ObjectMeta: metav1.ObjectMeta{ 327 Name: "baz", 328 Namespace: "default", 329 }, 330 Spec: bootstrapv1.KubeadmConfigSpec{ 331 Format: bootstrapv1.Ignition, 332 DiskSetup: &bootstrapv1.DiskSetup{ 333 Partitions: []bootstrapv1.Partition{ 334 { 335 TableType: ptr.To("MS-DOS"), 336 }, 337 }, 338 }, 339 }, 340 }, 341 expectErr: true, 342 }, 343 "format is Ignition, experimental retry join is set": { 344 enableIgnitionFeature: true, 345 in: &bootstrapv1.KubeadmConfig{ 346 ObjectMeta: metav1.ObjectMeta{ 347 Name: "baz", 348 Namespace: "default", 349 }, 350 Spec: bootstrapv1.KubeadmConfigSpec{ 351 Format: bootstrapv1.Ignition, 352 UseExperimentalRetryJoin: true, 353 }, 354 }, 355 expectErr: true, 356 }, 357 "feature gate disabled, format is Ignition": { 358 in: &bootstrapv1.KubeadmConfig{ 359 ObjectMeta: metav1.ObjectMeta{ 360 Name: "baz", 361 Namespace: "default", 362 }, 363 Spec: bootstrapv1.KubeadmConfigSpec{ 364 Format: bootstrapv1.Ignition, 365 }, 366 }, 367 expectErr: true, 368 }, 369 "feature gate disabled, Ignition field is set": { 370 in: &bootstrapv1.KubeadmConfig{ 371 ObjectMeta: metav1.ObjectMeta{ 372 Name: "baz", 373 Namespace: "default", 374 }, 375 Spec: bootstrapv1.KubeadmConfigSpec{ 376 Format: bootstrapv1.Ignition, 377 Ignition: &bootstrapv1.IgnitionSpec{ 378 ContainerLinuxConfig: &bootstrapv1.ContainerLinuxConfig{}, 379 }, 380 }, 381 }, 382 expectErr: true, 383 }, 384 "replaceFS specified with Ignition": { 385 enableIgnitionFeature: true, 386 in: &bootstrapv1.KubeadmConfig{ 387 ObjectMeta: metav1.ObjectMeta{ 388 Name: "baz", 389 Namespace: "default", 390 }, 391 Spec: bootstrapv1.KubeadmConfigSpec{ 392 Format: bootstrapv1.Ignition, 393 DiskSetup: &bootstrapv1.DiskSetup{ 394 Filesystems: []bootstrapv1.Filesystem{ 395 { 396 ReplaceFS: ptr.To("ntfs"), 397 }, 398 }, 399 }, 400 }, 401 }, 402 expectErr: true, 403 }, 404 "filesystem partition specified with Ignition": { 405 enableIgnitionFeature: true, 406 in: &bootstrapv1.KubeadmConfig{ 407 ObjectMeta: metav1.ObjectMeta{ 408 Name: "baz", 409 Namespace: "default", 410 }, 411 Spec: bootstrapv1.KubeadmConfigSpec{ 412 Format: bootstrapv1.Ignition, 413 DiskSetup: &bootstrapv1.DiskSetup{ 414 Filesystems: []bootstrapv1.Filesystem{ 415 { 416 Partition: ptr.To("1"), 417 }, 418 }, 419 }, 420 }, 421 }, 422 expectErr: true, 423 }, 424 "file encoding gzip specified with Ignition": { 425 enableIgnitionFeature: true, 426 in: &bootstrapv1.KubeadmConfig{ 427 ObjectMeta: metav1.ObjectMeta{ 428 Name: "baz", 429 Namespace: "default", 430 }, 431 Spec: bootstrapv1.KubeadmConfigSpec{ 432 Format: bootstrapv1.Ignition, 433 Files: []bootstrapv1.File{ 434 { 435 Encoding: bootstrapv1.Gzip, 436 }, 437 }, 438 }, 439 }, 440 expectErr: true, 441 }, 442 "file encoding gzip+base64 specified with Ignition": { 443 enableIgnitionFeature: true, 444 in: &bootstrapv1.KubeadmConfig{ 445 ObjectMeta: metav1.ObjectMeta{ 446 Name: "baz", 447 Namespace: "default", 448 }, 449 Spec: bootstrapv1.KubeadmConfigSpec{ 450 Format: bootstrapv1.Ignition, 451 Files: []bootstrapv1.File{ 452 { 453 Encoding: bootstrapv1.GzipBase64, 454 }, 455 }, 456 }, 457 }, 458 expectErr: true, 459 }, 460 } 461 462 for name, tt := range cases { 463 t.Run(name, func(t *testing.T) { 464 if tt.enableIgnitionFeature { 465 // NOTE: KubeadmBootstrapFormatIgnition feature flag is disabled by default. 466 // Enabling the feature flag temporarily for this test. 467 defer utilfeature.SetFeatureGateDuringTest(t, feature.Gates, feature.KubeadmBootstrapFormatIgnition, true)() 468 } 469 g := NewWithT(t) 470 471 webhook := &KubeadmConfig{} 472 473 if tt.expectErr { 474 warnings, err := webhook.ValidateCreate(ctx, tt.in) 475 g.Expect(err).To(HaveOccurred()) 476 g.Expect(warnings).To(BeEmpty()) 477 warnings, err = webhook.ValidateUpdate(ctx, nil, tt.in) 478 g.Expect(err).To(HaveOccurred()) 479 g.Expect(warnings).To(BeEmpty()) 480 } else { 481 warnings, err := webhook.ValidateCreate(ctx, tt.in) 482 g.Expect(err).ToNot(HaveOccurred()) 483 g.Expect(warnings).To(BeEmpty()) 484 warnings, err = webhook.ValidateUpdate(ctx, nil, tt.in) 485 g.Expect(err).ToNot(HaveOccurred()) 486 g.Expect(warnings).To(BeEmpty()) 487 } 488 }) 489 } 490 }