sigs.k8s.io/cluster-api@v1.6.3/internal/webhooks/patch_validation_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 corev1 "k8s.io/api/core/v1" 24 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/apimachinery/pkg/util/validation/field" 27 utilfeature "k8s.io/component-base/featuregate/testing" 28 "k8s.io/utils/pointer" 29 30 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 31 "sigs.k8s.io/cluster-api/feature" 32 "sigs.k8s.io/cluster-api/internal/test/builder" 33 ) 34 35 func TestValidatePatches(t *testing.T) { 36 tests := []struct { 37 name string 38 clusterClass clusterv1.ClusterClass 39 runtimeSDK bool 40 wantErr bool 41 }{ 42 { 43 name: "pass multiple patches that are correctly formatted", 44 clusterClass: clusterv1.ClusterClass{ 45 Spec: clusterv1.ClusterClassSpec{ 46 ControlPlane: clusterv1.ControlPlaneClass{ 47 LocalObjectTemplate: clusterv1.LocalObjectTemplate{ 48 Ref: &corev1.ObjectReference{ 49 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 50 Kind: "ControlPlaneTemplate", 51 }, 52 }, 53 }, 54 55 Patches: []clusterv1.ClusterClassPatch{ 56 { 57 Name: "patch1", 58 Definitions: []clusterv1.PatchDefinition{ 59 { 60 Selector: clusterv1.PatchSelector{ 61 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 62 Kind: "ControlPlaneTemplate", 63 MatchResources: clusterv1.PatchSelectorMatch{ 64 ControlPlane: true, 65 }, 66 }, 67 JSONPatches: []clusterv1.JSONPatch{ 68 { 69 Op: "add", 70 Path: "/spec/template/spec/variableSetting/variableValue1", 71 ValueFrom: &clusterv1.JSONPatchValue{ 72 Variable: pointer.String("variableName1"), 73 }, 74 }, 75 }, 76 }, 77 }, 78 }, 79 { 80 Name: "patch2", 81 Definitions: []clusterv1.PatchDefinition{ 82 { 83 Selector: clusterv1.PatchSelector{ 84 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 85 Kind: "ControlPlaneTemplate", 86 MatchResources: clusterv1.PatchSelectorMatch{ 87 ControlPlane: true, 88 }, 89 }, 90 JSONPatches: []clusterv1.JSONPatch{ 91 { 92 Op: "add", 93 Path: "/spec/template/spec/variableSetting/variableValue2", 94 ValueFrom: &clusterv1.JSONPatchValue{ 95 Variable: pointer.String("variableName2"), 96 }, 97 }, 98 }, 99 }, 100 }, 101 }, 102 }, 103 Variables: []clusterv1.ClusterClassVariable{ 104 { 105 Name: "variableName1", 106 Required: true, 107 Schema: clusterv1.VariableSchema{ 108 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 109 Type: "string", 110 }, 111 }, 112 }, 113 { 114 Name: "variableName2", 115 Required: true, 116 Schema: clusterv1.VariableSchema{ 117 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 118 Type: "string", 119 }, 120 }, 121 }, 122 }, 123 }, 124 }, 125 wantErr: false, 126 }, 127 128 // Patch name validation 129 { 130 name: "error if patch name is empty", 131 clusterClass: clusterv1.ClusterClass{ 132 Spec: clusterv1.ClusterClassSpec{ 133 ControlPlane: clusterv1.ControlPlaneClass{ 134 LocalObjectTemplate: clusterv1.LocalObjectTemplate{ 135 Ref: &corev1.ObjectReference{ 136 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 137 Kind: "ControlPlaneTemplate", 138 }, 139 }, 140 }, 141 142 Patches: []clusterv1.ClusterClassPatch{ 143 144 { 145 Name: "", 146 Definitions: []clusterv1.PatchDefinition{ 147 { 148 Selector: clusterv1.PatchSelector{ 149 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 150 Kind: "ControlPlaneTemplate", 151 MatchResources: clusterv1.PatchSelectorMatch{ 152 ControlPlane: true, 153 }, 154 }, 155 JSONPatches: []clusterv1.JSONPatch{ 156 { 157 Op: "add", 158 Path: "/spec/template/spec/kubeadmConfigSpec/clusterConfiguration/controllerManager/extraArgs/cluster-name", 159 ValueFrom: &clusterv1.JSONPatchValue{ 160 Variable: pointer.String("variableName"), 161 }, 162 }, 163 }, 164 }, 165 }, 166 }, 167 }, 168 Variables: []clusterv1.ClusterClassVariable{ 169 { 170 Name: "variableName", 171 Required: true, 172 Schema: clusterv1.VariableSchema{ 173 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 174 Type: "string", 175 }, 176 }, 177 }, 178 }, 179 }, 180 }, 181 wantErr: true, 182 }, 183 { 184 name: "error if patches name is not unique", 185 clusterClass: clusterv1.ClusterClass{ 186 Spec: clusterv1.ClusterClassSpec{ 187 ControlPlane: clusterv1.ControlPlaneClass{ 188 LocalObjectTemplate: clusterv1.LocalObjectTemplate{ 189 Ref: &corev1.ObjectReference{ 190 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 191 Kind: "ControlPlaneTemplate", 192 }, 193 }, 194 }, 195 196 Patches: []clusterv1.ClusterClassPatch{ 197 198 { 199 Name: "patch1", 200 Definitions: []clusterv1.PatchDefinition{ 201 { 202 Selector: clusterv1.PatchSelector{ 203 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 204 Kind: "ControlPlaneTemplate", 205 MatchResources: clusterv1.PatchSelectorMatch{ 206 ControlPlane: true, 207 }, 208 }, 209 JSONPatches: []clusterv1.JSONPatch{ 210 { 211 Op: "add", 212 Path: "/spec/template/spec/kubeadmConfigSpec/clusterConfiguration/controllerManager/extraArgs/cluster-name", 213 ValueFrom: &clusterv1.JSONPatchValue{ 214 Variable: pointer.String("variableName1"), 215 }, 216 }, 217 }, 218 }, 219 }, 220 }, 221 { 222 Name: "patch1", 223 Definitions: []clusterv1.PatchDefinition{ 224 { 225 Selector: clusterv1.PatchSelector{ 226 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 227 Kind: "ControlPlaneTemplate", 228 MatchResources: clusterv1.PatchSelectorMatch{ 229 ControlPlane: true, 230 }, 231 }, 232 JSONPatches: []clusterv1.JSONPatch{ 233 { 234 Op: "add", 235 Path: "/spec/template/spec/variableSetting/variableValue", 236 ValueFrom: &clusterv1.JSONPatchValue{ 237 Variable: pointer.String("variableName2"), 238 }, 239 }, 240 }, 241 }, 242 }, 243 }, 244 }, 245 Variables: []clusterv1.ClusterClassVariable{ 246 { 247 Name: "variableName1", 248 Required: true, 249 Schema: clusterv1.VariableSchema{ 250 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 251 Type: "string", 252 }, 253 }, 254 }, 255 { 256 Name: "variableName2", 257 Required: true, 258 Schema: clusterv1.VariableSchema{ 259 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 260 Type: "string", 261 }, 262 }, 263 }, 264 }, 265 }, 266 }, 267 wantErr: true, 268 }, 269 270 // enabledIf validation 271 { 272 name: "pass if enabledIf is a valid Go template", 273 clusterClass: clusterv1.ClusterClass{ 274 Spec: clusterv1.ClusterClassSpec{ 275 ControlPlane: clusterv1.ControlPlaneClass{ 276 LocalObjectTemplate: clusterv1.LocalObjectTemplate{ 277 Ref: &corev1.ObjectReference{ 278 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 279 Kind: "ControlPlaneTemplate", 280 }, 281 }, 282 }, 283 Patches: []clusterv1.ClusterClassPatch{ 284 { 285 Name: "patch1", 286 EnabledIf: pointer.String(`template {{ .variableB }}`), 287 Definitions: []clusterv1.PatchDefinition{}, 288 }, 289 }, 290 }, 291 }, 292 wantErr: false, 293 }, 294 { 295 name: "error if enabledIf is an invalid Go template", 296 clusterClass: clusterv1.ClusterClass{ 297 Spec: clusterv1.ClusterClassSpec{ 298 ControlPlane: clusterv1.ControlPlaneClass{ 299 LocalObjectTemplate: clusterv1.LocalObjectTemplate{ 300 Ref: &corev1.ObjectReference{ 301 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 302 Kind: "ControlPlaneTemplate", 303 }, 304 }, 305 }, 306 Patches: []clusterv1.ClusterClassPatch{ 307 { 308 Name: "patch1", 309 EnabledIf: pointer.String(`template {{{{{{{{ .variableB }}`), 310 }, 311 }, 312 }, 313 }, 314 wantErr: true, 315 }, 316 // Patch "op" (operation) validation 317 { 318 name: "error if patch op is not \"add\" \"remove\" or \"replace\"", 319 clusterClass: clusterv1.ClusterClass{ 320 Spec: clusterv1.ClusterClassSpec{ 321 ControlPlane: clusterv1.ControlPlaneClass{ 322 LocalObjectTemplate: clusterv1.LocalObjectTemplate{ 323 Ref: &corev1.ObjectReference{ 324 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 325 Kind: "ControlPlaneTemplate", 326 }, 327 }, 328 }, 329 330 Patches: []clusterv1.ClusterClassPatch{ 331 332 { 333 Name: "patch1", 334 Definitions: []clusterv1.PatchDefinition{ 335 { 336 Selector: clusterv1.PatchSelector{ 337 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 338 Kind: "ControlPlaneTemplate", 339 MatchResources: clusterv1.PatchSelectorMatch{ 340 ControlPlane: true, 341 }, 342 }, 343 JSONPatches: []clusterv1.JSONPatch{ 344 { 345 // OP is set to an unrecognized value here. 346 Op: "drop", 347 Path: "/spec/template/spec/variableSetting/variableValue2", 348 }, 349 }, 350 }, 351 }, 352 }, 353 }, 354 }, 355 }, 356 wantErr: true, 357 }, 358 359 // Patch path validation 360 { 361 name: "error if jsonPath does not begin with \"/spec/\"", 362 clusterClass: clusterv1.ClusterClass{ 363 Spec: clusterv1.ClusterClassSpec{ 364 ControlPlane: clusterv1.ControlPlaneClass{ 365 LocalObjectTemplate: clusterv1.LocalObjectTemplate{ 366 Ref: &corev1.ObjectReference{ 367 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 368 Kind: "ControlPlaneTemplate", 369 }, 370 }, 371 }, 372 373 Patches: []clusterv1.ClusterClassPatch{ 374 375 { 376 Name: "patch1", 377 Definitions: []clusterv1.PatchDefinition{ 378 { 379 Selector: clusterv1.PatchSelector{ 380 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 381 Kind: "ControlPlaneTemplate", 382 MatchResources: clusterv1.PatchSelectorMatch{ 383 ControlPlane: true, 384 }, 385 }, 386 JSONPatches: []clusterv1.JSONPatch{ 387 { 388 Op: "remove", 389 // Path is set to status. 390 Path: "/status/template/spec/variableSetting/variableValue2", 391 }, 392 }, 393 }, 394 }, 395 }, 396 }, 397 }, 398 }, 399 wantErr: true, 400 }, 401 { 402 name: "pass if jsonPatch path uses a valid index for add i.e. 0", 403 clusterClass: clusterv1.ClusterClass{ 404 Spec: clusterv1.ClusterClassSpec{ 405 ControlPlane: clusterv1.ControlPlaneClass{ 406 LocalObjectTemplate: clusterv1.LocalObjectTemplate{ 407 Ref: &corev1.ObjectReference{ 408 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 409 Kind: "ControlPlaneTemplate", 410 }, 411 }, 412 }, 413 414 Patches: []clusterv1.ClusterClassPatch{ 415 { 416 Name: "patch1", 417 Definitions: []clusterv1.PatchDefinition{ 418 { 419 Selector: clusterv1.PatchSelector{ 420 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 421 Kind: "ControlPlaneTemplate", 422 MatchResources: clusterv1.PatchSelectorMatch{ 423 ControlPlane: true, 424 }, 425 }, 426 JSONPatches: []clusterv1.JSONPatch{ 427 { 428 Op: "add", 429 Path: "/spec/template/0/", 430 ValueFrom: &clusterv1.JSONPatchValue{ 431 Variable: pointer.String("variableName"), 432 }, 433 }, 434 }, 435 }, 436 }, 437 }, 438 }, 439 Variables: []clusterv1.ClusterClassVariable{ 440 { 441 Name: "variableName", 442 Required: true, 443 Schema: clusterv1.VariableSchema{ 444 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 445 Type: "string", 446 }, 447 }, 448 }, 449 }, 450 }, 451 }, 452 }, 453 { 454 name: "error if jsonPatch path uses an invalid index for add i.e. a number greater than 0.", 455 clusterClass: clusterv1.ClusterClass{ 456 Spec: clusterv1.ClusterClassSpec{ 457 ControlPlane: clusterv1.ControlPlaneClass{ 458 LocalObjectTemplate: clusterv1.LocalObjectTemplate{ 459 Ref: &corev1.ObjectReference{ 460 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 461 Kind: "ControlPlaneTemplate", 462 }, 463 }, 464 }, 465 466 Patches: []clusterv1.ClusterClassPatch{ 467 468 { 469 Name: "patch1", 470 Definitions: []clusterv1.PatchDefinition{ 471 { 472 Selector: clusterv1.PatchSelector{ 473 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 474 Kind: "ControlPlaneTemplate", 475 MatchResources: clusterv1.PatchSelectorMatch{ 476 ControlPlane: true, 477 }, 478 }, 479 JSONPatches: []clusterv1.JSONPatch{ 480 { 481 Op: "add", 482 Path: "/spec/template/1/", 483 ValueFrom: &clusterv1.JSONPatchValue{ 484 Variable: pointer.String("variableName"), 485 }, 486 }, 487 }, 488 }, 489 }, 490 }, 491 }, 492 Variables: []clusterv1.ClusterClassVariable{ 493 { 494 Name: "variableName", 495 Required: true, 496 Schema: clusterv1.VariableSchema{ 497 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 498 Type: "string", 499 }, 500 }, 501 }, 502 }, 503 }, 504 }, 505 wantErr: true, 506 }, 507 { 508 name: "error if jsonPatch path uses an invalid index for add i.e. 01", 509 clusterClass: clusterv1.ClusterClass{ 510 Spec: clusterv1.ClusterClassSpec{ 511 ControlPlane: clusterv1.ControlPlaneClass{ 512 LocalObjectTemplate: clusterv1.LocalObjectTemplate{ 513 Ref: &corev1.ObjectReference{ 514 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 515 Kind: "ControlPlaneTemplate", 516 }, 517 }, 518 }, 519 520 Patches: []clusterv1.ClusterClassPatch{ 521 522 { 523 Name: "patch1", 524 Definitions: []clusterv1.PatchDefinition{ 525 { 526 Selector: clusterv1.PatchSelector{ 527 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 528 Kind: "ControlPlaneTemplate", 529 MatchResources: clusterv1.PatchSelectorMatch{ 530 ControlPlane: true, 531 }, 532 }, 533 JSONPatches: []clusterv1.JSONPatch{ 534 { 535 Op: "add", 536 Path: "/spec/template/01/", 537 ValueFrom: &clusterv1.JSONPatchValue{ 538 Variable: pointer.String("variableName"), 539 }, 540 }, 541 }, 542 }, 543 }, 544 }, 545 }, 546 Variables: []clusterv1.ClusterClassVariable{ 547 { 548 Name: "variableName", 549 Required: true, 550 Schema: clusterv1.VariableSchema{ 551 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 552 Type: "string", 553 }, 554 }, 555 }, 556 }, 557 }, 558 }, 559 wantErr: true, 560 }, 561 { 562 name: "error if jsonPatch path uses any index for remove i.e. 0 or -.", 563 clusterClass: clusterv1.ClusterClass{ 564 Spec: clusterv1.ClusterClassSpec{ 565 ControlPlane: clusterv1.ControlPlaneClass{ 566 LocalObjectTemplate: clusterv1.LocalObjectTemplate{ 567 Ref: &corev1.ObjectReference{ 568 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 569 Kind: "ControlPlaneTemplate", 570 }, 571 }, 572 }, 573 574 Patches: []clusterv1.ClusterClassPatch{ 575 576 { 577 Name: "patch1", 578 Definitions: []clusterv1.PatchDefinition{ 579 { 580 Selector: clusterv1.PatchSelector{ 581 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 582 Kind: "ControlPlaneTemplate", 583 MatchResources: clusterv1.PatchSelectorMatch{ 584 ControlPlane: true, 585 }, 586 }, 587 JSONPatches: []clusterv1.JSONPatch{ 588 { 589 Op: "remove", 590 Path: "/spec/template/0/", 591 ValueFrom: &clusterv1.JSONPatchValue{ 592 Variable: pointer.String("variableName"), 593 }, 594 }, 595 }, 596 }, 597 }, 598 }, 599 }, 600 Variables: []clusterv1.ClusterClassVariable{ 601 { 602 Name: "variableName", 603 Required: true, 604 Schema: clusterv1.VariableSchema{ 605 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 606 Type: "string", 607 }, 608 }, 609 }, 610 }, 611 }, 612 }, 613 wantErr: true, 614 }, 615 { 616 name: "error if jsonPatch path uses any index for replace i.e. 0", 617 clusterClass: clusterv1.ClusterClass{ 618 Spec: clusterv1.ClusterClassSpec{ 619 ControlPlane: clusterv1.ControlPlaneClass{ 620 LocalObjectTemplate: clusterv1.LocalObjectTemplate{ 621 Ref: &corev1.ObjectReference{ 622 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 623 Kind: "ControlPlaneTemplate", 624 }, 625 }, 626 }, 627 628 Patches: []clusterv1.ClusterClassPatch{ 629 630 { 631 Name: "patch1", 632 Definitions: []clusterv1.PatchDefinition{ 633 { 634 Selector: clusterv1.PatchSelector{ 635 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 636 Kind: "ControlPlaneTemplate", 637 MatchResources: clusterv1.PatchSelectorMatch{ 638 ControlPlane: true, 639 }, 640 }, 641 JSONPatches: []clusterv1.JSONPatch{ 642 { 643 Op: "replace", 644 Path: "/spec/template/0/", 645 ValueFrom: &clusterv1.JSONPatchValue{ 646 Variable: pointer.String("variableName"), 647 }, 648 }, 649 }, 650 }, 651 }, 652 }, 653 }, 654 Variables: []clusterv1.ClusterClassVariable{ 655 { 656 Name: "variableName", 657 Required: true, 658 Schema: clusterv1.VariableSchema{ 659 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 660 Type: "string", 661 }, 662 }, 663 }, 664 }, 665 }, 666 }, 667 wantErr: true, 668 }, 669 670 // Patch Value/ValueFrom validation 671 { 672 name: "error if jsonPatch has neither Value nor ValueFrom", 673 clusterClass: clusterv1.ClusterClass{ 674 Spec: clusterv1.ClusterClassSpec{ 675 ControlPlane: clusterv1.ControlPlaneClass{ 676 LocalObjectTemplate: clusterv1.LocalObjectTemplate{ 677 Ref: &corev1.ObjectReference{ 678 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 679 Kind: "ControlPlaneTemplate", 680 }, 681 }, 682 }, 683 684 Patches: []clusterv1.ClusterClassPatch{ 685 686 { 687 Name: "patch1", 688 Definitions: []clusterv1.PatchDefinition{ 689 { 690 Selector: clusterv1.PatchSelector{ 691 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 692 Kind: "ControlPlaneTemplate", 693 MatchResources: clusterv1.PatchSelectorMatch{ 694 ControlPlane: true, 695 }, 696 }, 697 JSONPatches: []clusterv1.JSONPatch{ 698 { 699 Op: "add", 700 Path: "/spec/template/spec/", 701 // Value and ValueFrom not defined. 702 }, 703 }, 704 }, 705 }, 706 }, 707 }, 708 }, 709 }, 710 wantErr: true, 711 }, 712 { 713 name: "error if jsonPatch has both Value and ValueFrom", 714 clusterClass: clusterv1.ClusterClass{ 715 Spec: clusterv1.ClusterClassSpec{ 716 ControlPlane: clusterv1.ControlPlaneClass{ 717 LocalObjectTemplate: clusterv1.LocalObjectTemplate{ 718 Ref: &corev1.ObjectReference{ 719 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 720 Kind: "ControlPlaneTemplate", 721 }, 722 }, 723 }, 724 Patches: []clusterv1.ClusterClassPatch{ 725 726 { 727 Name: "patch1", 728 Definitions: []clusterv1.PatchDefinition{ 729 { 730 Selector: clusterv1.PatchSelector{ 731 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 732 Kind: "ControlPlaneTemplate", 733 MatchResources: clusterv1.PatchSelectorMatch{ 734 ControlPlane: true, 735 }, 736 }, 737 JSONPatches: []clusterv1.JSONPatch{ 738 { 739 Op: "add", 740 Path: "/spec/template/spec/", 741 ValueFrom: &clusterv1.JSONPatchValue{ 742 Variable: pointer.String("variableName"), 743 }, 744 Value: &apiextensionsv1.JSON{Raw: []byte("1")}, 745 }, 746 }, 747 }, 748 }, 749 }, 750 }, 751 Variables: []clusterv1.ClusterClassVariable{ 752 { 753 Name: "variableName", 754 Required: true, 755 Schema: clusterv1.VariableSchema{ 756 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 757 Type: "string", 758 }, 759 }, 760 }, 761 }, 762 }, 763 }, 764 wantErr: true, 765 }, 766 767 // Patch value validation 768 { 769 name: "pass if jsonPatch value is valid json literal", 770 clusterClass: clusterv1.ClusterClass{ 771 Spec: clusterv1.ClusterClassSpec{ 772 ControlPlane: clusterv1.ControlPlaneClass{ 773 LocalObjectTemplate: clusterv1.LocalObjectTemplate{ 774 Ref: &corev1.ObjectReference{ 775 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 776 Kind: "ControlPlaneTemplate", 777 }, 778 }, 779 }, 780 Patches: []clusterv1.ClusterClassPatch{ 781 782 { 783 Name: "patch1", 784 Definitions: []clusterv1.PatchDefinition{ 785 { 786 Selector: clusterv1.PatchSelector{ 787 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 788 Kind: "ControlPlaneTemplate", 789 MatchResources: clusterv1.PatchSelectorMatch{ 790 ControlPlane: true, 791 }, 792 }, 793 JSONPatches: []clusterv1.JSONPatch{ 794 { 795 Op: "add", 796 Path: "/spec/template/spec/", 797 Value: &apiextensionsv1.JSON{Raw: []byte(`"stringValue"`)}, 798 }, 799 }, 800 }, 801 }, 802 }, 803 }, 804 }, 805 }, 806 wantErr: false, 807 }, 808 { 809 name: "pass if jsonPatch value is valid json", 810 clusterClass: clusterv1.ClusterClass{ 811 Spec: clusterv1.ClusterClassSpec{ 812 ControlPlane: clusterv1.ControlPlaneClass{ 813 LocalObjectTemplate: clusterv1.LocalObjectTemplate{ 814 Ref: &corev1.ObjectReference{ 815 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 816 Kind: "ControlPlaneTemplate", 817 }, 818 }, 819 }, 820 Patches: []clusterv1.ClusterClassPatch{ 821 822 { 823 Name: "patch1", 824 Definitions: []clusterv1.PatchDefinition{ 825 { 826 Selector: clusterv1.PatchSelector{ 827 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 828 Kind: "ControlPlaneTemplate", 829 MatchResources: clusterv1.PatchSelectorMatch{ 830 ControlPlane: true, 831 }, 832 }, 833 JSONPatches: []clusterv1.JSONPatch{ 834 { 835 Op: "add", 836 Path: "/spec/template/spec/", 837 Value: &apiextensionsv1.JSON{Raw: []byte( 838 "{\"id\": \"file\"" + 839 "," + 840 "\"value\": \"File\"}")}, 841 }, 842 }, 843 }, 844 }, 845 }, 846 }, 847 }, 848 }, 849 wantErr: false, 850 }, 851 { 852 name: "pass if jsonPatch value is nil", 853 clusterClass: clusterv1.ClusterClass{ 854 Spec: clusterv1.ClusterClassSpec{ 855 ControlPlane: clusterv1.ControlPlaneClass{ 856 LocalObjectTemplate: clusterv1.LocalObjectTemplate{ 857 Ref: &corev1.ObjectReference{ 858 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 859 Kind: "ControlPlaneTemplate", 860 }, 861 }, 862 }, 863 Patches: []clusterv1.ClusterClassPatch{ 864 865 { 866 Name: "patch1", 867 Definitions: []clusterv1.PatchDefinition{ 868 { 869 Selector: clusterv1.PatchSelector{ 870 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 871 Kind: "ControlPlaneTemplate", 872 MatchResources: clusterv1.PatchSelectorMatch{ 873 ControlPlane: true, 874 }, 875 }, 876 JSONPatches: []clusterv1.JSONPatch{ 877 { 878 Op: "add", 879 Path: "/spec/template/spec/", 880 Value: &apiextensionsv1.JSON{ 881 Raw: nil, 882 }, 883 }, 884 }, 885 }, 886 }, 887 }, 888 }, 889 }, 890 }, 891 wantErr: false, 892 }, 893 { 894 name: "error if jsonPatch value is invalid json", 895 clusterClass: clusterv1.ClusterClass{ 896 Spec: clusterv1.ClusterClassSpec{ 897 ControlPlane: clusterv1.ControlPlaneClass{ 898 LocalObjectTemplate: clusterv1.LocalObjectTemplate{ 899 Ref: &corev1.ObjectReference{ 900 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 901 Kind: "ControlPlaneTemplate", 902 }, 903 }, 904 }, 905 906 Patches: []clusterv1.ClusterClassPatch{ 907 908 { 909 Name: "patch1", 910 Definitions: []clusterv1.PatchDefinition{ 911 { 912 Selector: clusterv1.PatchSelector{ 913 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 914 Kind: "ControlPlaneTemplate", 915 MatchResources: clusterv1.PatchSelectorMatch{ 916 ControlPlane: true, 917 }, 918 }, 919 JSONPatches: []clusterv1.JSONPatch{ 920 { 921 Op: "add", 922 Path: "/spec/template/spec/", 923 Value: &apiextensionsv1.JSON{Raw: []byte( 924 "{\"id\": \"file\"" + 925 // missing comma here + 926 "\"value\": \"File\"}")}, 927 }, 928 }, 929 }, 930 }, 931 }, 932 }, 933 }, 934 }, 935 wantErr: true, 936 }, 937 938 // Patch valueFrom validation 939 { 940 name: "error if jsonPatch defines neither ValueFrom.Template nor ValueFrom.Variable", 941 clusterClass: clusterv1.ClusterClass{ 942 Spec: clusterv1.ClusterClassSpec{ 943 ControlPlane: clusterv1.ControlPlaneClass{ 944 LocalObjectTemplate: clusterv1.LocalObjectTemplate{ 945 Ref: &corev1.ObjectReference{ 946 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 947 Kind: "ControlPlaneTemplate", 948 }, 949 }, 950 }, 951 Patches: []clusterv1.ClusterClassPatch{ 952 { 953 Name: "patch1", 954 Definitions: []clusterv1.PatchDefinition{ 955 { 956 Selector: clusterv1.PatchSelector{ 957 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 958 Kind: "ControlPlaneTemplate", 959 MatchResources: clusterv1.PatchSelectorMatch{ 960 ControlPlane: true, 961 }, 962 }, 963 JSONPatches: []clusterv1.JSONPatch{ 964 { 965 Op: "add", 966 Path: "/spec/template/spec/", 967 ValueFrom: &clusterv1.JSONPatchValue{}, 968 }, 969 }, 970 }, 971 }, 972 }, 973 }, 974 }, 975 }, 976 wantErr: true, 977 }, 978 { 979 name: "error if jsonPatch has both ValueFrom.Template and ValueFrom.Variable", 980 clusterClass: clusterv1.ClusterClass{ 981 Spec: clusterv1.ClusterClassSpec{ 982 ControlPlane: clusterv1.ControlPlaneClass{ 983 LocalObjectTemplate: clusterv1.LocalObjectTemplate{ 984 Ref: &corev1.ObjectReference{ 985 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 986 Kind: "ControlPlaneTemplate", 987 }, 988 }, 989 }, 990 Patches: []clusterv1.ClusterClassPatch{ 991 { 992 Name: "patch1", 993 Definitions: []clusterv1.PatchDefinition{ 994 { 995 Selector: clusterv1.PatchSelector{ 996 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 997 Kind: "ControlPlaneTemplate", 998 MatchResources: clusterv1.PatchSelectorMatch{ 999 ControlPlane: true, 1000 }, 1001 }, 1002 JSONPatches: []clusterv1.JSONPatch{ 1003 { 1004 Op: "add", 1005 Path: "/spec/template/spec/", 1006 ValueFrom: &clusterv1.JSONPatchValue{ 1007 Variable: pointer.String("variableName"), 1008 Template: pointer.String(`template {{ .variableB }}`), 1009 }, 1010 }, 1011 }, 1012 }, 1013 }, 1014 }, 1015 }, 1016 Variables: []clusterv1.ClusterClassVariable{ 1017 { 1018 Name: "variableName", 1019 Required: true, 1020 Schema: clusterv1.VariableSchema{ 1021 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 1022 Type: "string", 1023 }, 1024 }, 1025 }, 1026 }, 1027 }, 1028 }, 1029 wantErr: true, 1030 }, 1031 1032 // Patch valueFrom.Template validation 1033 { 1034 name: "pass if jsonPatch defines a valid ValueFrom.Template", 1035 clusterClass: clusterv1.ClusterClass{ 1036 Spec: clusterv1.ClusterClassSpec{ 1037 ControlPlane: clusterv1.ControlPlaneClass{ 1038 LocalObjectTemplate: clusterv1.LocalObjectTemplate{ 1039 Ref: &corev1.ObjectReference{ 1040 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 1041 Kind: "ControlPlaneTemplate", 1042 }, 1043 }, 1044 }, 1045 Patches: []clusterv1.ClusterClassPatch{ 1046 { 1047 Name: "patch1", 1048 Definitions: []clusterv1.PatchDefinition{ 1049 { 1050 Selector: clusterv1.PatchSelector{ 1051 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 1052 Kind: "ControlPlaneTemplate", 1053 MatchResources: clusterv1.PatchSelectorMatch{ 1054 ControlPlane: true, 1055 }, 1056 }, 1057 JSONPatches: []clusterv1.JSONPatch{ 1058 { 1059 Op: "add", 1060 Path: "/spec/template/spec/", 1061 ValueFrom: &clusterv1.JSONPatchValue{ 1062 Template: pointer.String(`template {{ .variableB }}`), 1063 }, 1064 }, 1065 }, 1066 }, 1067 }, 1068 }, 1069 }, 1070 Variables: []clusterv1.ClusterClassVariable{ 1071 { 1072 Name: "variableName", 1073 Required: true, 1074 Schema: clusterv1.VariableSchema{ 1075 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 1076 Type: "string", 1077 }, 1078 }, 1079 }, 1080 }, 1081 }, 1082 }, 1083 wantErr: false, 1084 }, 1085 { 1086 name: "error if jsonPatch defines an invalid ValueFrom.Template", 1087 clusterClass: clusterv1.ClusterClass{ 1088 Spec: clusterv1.ClusterClassSpec{ 1089 ControlPlane: clusterv1.ControlPlaneClass{ 1090 LocalObjectTemplate: clusterv1.LocalObjectTemplate{ 1091 Ref: &corev1.ObjectReference{ 1092 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 1093 Kind: "ControlPlaneTemplate", 1094 }, 1095 }, 1096 }, 1097 Patches: []clusterv1.ClusterClassPatch{ 1098 { 1099 Name: "patch1", 1100 Definitions: []clusterv1.PatchDefinition{ 1101 { 1102 Selector: clusterv1.PatchSelector{ 1103 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 1104 Kind: "ControlPlaneTemplate", 1105 MatchResources: clusterv1.PatchSelectorMatch{ 1106 ControlPlane: true, 1107 }, 1108 }, 1109 JSONPatches: []clusterv1.JSONPatch{ 1110 { 1111 Op: "add", 1112 Path: "/spec/template/spec/", 1113 ValueFrom: &clusterv1.JSONPatchValue{ 1114 // Template is invalid - too many leading curly braces. 1115 Template: pointer.String(`template {{{{{{{{ .variableB }}`), 1116 }, 1117 }, 1118 }, 1119 }, 1120 }, 1121 }, 1122 }, 1123 Variables: []clusterv1.ClusterClassVariable{ 1124 { 1125 Name: "variableName", 1126 Required: true, 1127 Schema: clusterv1.VariableSchema{ 1128 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 1129 Type: "string", 1130 }, 1131 }, 1132 }, 1133 }, 1134 }, 1135 }, 1136 wantErr: true, 1137 }, 1138 1139 // Patch valueFrom.Variable validation 1140 { 1141 name: "error if jsonPatch valueFrom uses a variable which is not defined", 1142 clusterClass: clusterv1.ClusterClass{ 1143 Spec: clusterv1.ClusterClassSpec{ 1144 ControlPlane: clusterv1.ControlPlaneClass{ 1145 LocalObjectTemplate: clusterv1.LocalObjectTemplate{ 1146 Ref: &corev1.ObjectReference{ 1147 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 1148 Kind: "ControlPlaneTemplate", 1149 }, 1150 }, 1151 }, 1152 Patches: []clusterv1.ClusterClassPatch{ 1153 { 1154 Name: "patch1", 1155 Definitions: []clusterv1.PatchDefinition{ 1156 { 1157 Selector: clusterv1.PatchSelector{ 1158 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 1159 Kind: "ControlPlaneTemplate", 1160 MatchResources: clusterv1.PatchSelectorMatch{ 1161 ControlPlane: true, 1162 }, 1163 }, 1164 JSONPatches: []clusterv1.JSONPatch{ 1165 { 1166 Op: "add", 1167 Path: "/spec/template/spec/", 1168 ValueFrom: &clusterv1.JSONPatchValue{ 1169 Variable: pointer.String("undefinedVariable"), 1170 }, 1171 }, 1172 }, 1173 }, 1174 }, 1175 }, 1176 }, 1177 Variables: []clusterv1.ClusterClassVariable{ 1178 { 1179 Name: "variableName", 1180 Required: true, 1181 Schema: clusterv1.VariableSchema{ 1182 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 1183 Type: "string", 1184 }, 1185 }, 1186 }, 1187 }, 1188 }, 1189 }, 1190 wantErr: true, 1191 }, 1192 { 1193 name: "pass if jsonPatch uses a user-defined variable which is defined", 1194 clusterClass: clusterv1.ClusterClass{ 1195 Spec: clusterv1.ClusterClassSpec{ 1196 ControlPlane: clusterv1.ControlPlaneClass{ 1197 LocalObjectTemplate: clusterv1.LocalObjectTemplate{ 1198 Ref: &corev1.ObjectReference{ 1199 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 1200 Kind: "ControlPlaneTemplate", 1201 }, 1202 }, 1203 }, 1204 Patches: []clusterv1.ClusterClassPatch{ 1205 { 1206 Name: "patch1", 1207 Definitions: []clusterv1.PatchDefinition{ 1208 { 1209 Selector: clusterv1.PatchSelector{ 1210 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 1211 Kind: "ControlPlaneTemplate", 1212 MatchResources: clusterv1.PatchSelectorMatch{ 1213 ControlPlane: true, 1214 }, 1215 }, 1216 JSONPatches: []clusterv1.JSONPatch{ 1217 { 1218 Op: "add", 1219 Path: "/spec/template/spec/", 1220 ValueFrom: &clusterv1.JSONPatchValue{ 1221 Variable: pointer.String("variableName"), 1222 }, 1223 }, 1224 }, 1225 }, 1226 }, 1227 }, 1228 }, 1229 Variables: []clusterv1.ClusterClassVariable{ 1230 { 1231 Name: "variableName", 1232 Required: true, 1233 Schema: clusterv1.VariableSchema{ 1234 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 1235 Type: "string", 1236 }, 1237 }, 1238 }, 1239 }, 1240 }, 1241 }, 1242 wantErr: false, 1243 }, 1244 { 1245 name: "pass if jsonPatch uses a nested user-defined variable which is defined", 1246 clusterClass: clusterv1.ClusterClass{ 1247 Spec: clusterv1.ClusterClassSpec{ 1248 ControlPlane: clusterv1.ControlPlaneClass{ 1249 LocalObjectTemplate: clusterv1.LocalObjectTemplate{ 1250 Ref: &corev1.ObjectReference{ 1251 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 1252 Kind: "ControlPlaneTemplate", 1253 }, 1254 }, 1255 }, 1256 Patches: []clusterv1.ClusterClassPatch{ 1257 { 1258 Name: "patch1", 1259 Definitions: []clusterv1.PatchDefinition{ 1260 { 1261 Selector: clusterv1.PatchSelector{ 1262 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 1263 Kind: "ControlPlaneTemplate", 1264 MatchResources: clusterv1.PatchSelectorMatch{ 1265 ControlPlane: true, 1266 }, 1267 }, 1268 JSONPatches: []clusterv1.JSONPatch{ 1269 { 1270 Op: "add", 1271 Path: "/spec/template/spec/", 1272 ValueFrom: &clusterv1.JSONPatchValue{ 1273 Variable: pointer.String("variableName.nestedField"), 1274 }, 1275 }, 1276 }, 1277 }, 1278 }, 1279 }, 1280 }, 1281 Variables: []clusterv1.ClusterClassVariable{ 1282 { 1283 Name: "variableName", 1284 Required: true, 1285 Schema: clusterv1.VariableSchema{ 1286 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 1287 Type: "object", 1288 Properties: map[string]clusterv1.JSONSchemaProps{ 1289 "nestedField": { 1290 Type: "string", 1291 }, 1292 }, 1293 }, 1294 }, 1295 }, 1296 }, 1297 }, 1298 }, 1299 wantErr: false, 1300 }, 1301 { 1302 name: "error if jsonPatch uses a builtin variable which is not defined", 1303 clusterClass: clusterv1.ClusterClass{ 1304 Spec: clusterv1.ClusterClassSpec{ 1305 ControlPlane: clusterv1.ControlPlaneClass{ 1306 LocalObjectTemplate: clusterv1.LocalObjectTemplate{ 1307 Ref: &corev1.ObjectReference{ 1308 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 1309 Kind: "ControlPlaneTemplate", 1310 }, 1311 }, 1312 }, 1313 Patches: []clusterv1.ClusterClassPatch{ 1314 { 1315 Name: "patch1", 1316 Definitions: []clusterv1.PatchDefinition{ 1317 { 1318 Selector: clusterv1.PatchSelector{ 1319 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 1320 Kind: "ControlPlaneTemplate", 1321 MatchResources: clusterv1.PatchSelectorMatch{ 1322 ControlPlane: true, 1323 }, 1324 }, 1325 JSONPatches: []clusterv1.JSONPatch{ 1326 { 1327 Op: "add", 1328 Path: "/spec/template/spec/", 1329 ValueFrom: &clusterv1.JSONPatchValue{ 1330 Variable: pointer.String("builtin.notDefined"), 1331 }, 1332 }, 1333 }, 1334 }, 1335 }, 1336 }, 1337 }, 1338 }, 1339 }, 1340 wantErr: true, 1341 }, 1342 { 1343 name: "pass if jsonPatch uses a builtin variable which is defined", 1344 clusterClass: clusterv1.ClusterClass{ 1345 Spec: clusterv1.ClusterClassSpec{ 1346 ControlPlane: clusterv1.ControlPlaneClass{ 1347 LocalObjectTemplate: clusterv1.LocalObjectTemplate{ 1348 Ref: &corev1.ObjectReference{ 1349 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 1350 Kind: "ControlPlaneTemplate", 1351 }, 1352 }, 1353 }, 1354 1355 Patches: []clusterv1.ClusterClassPatch{ 1356 1357 { 1358 Name: "patch1", 1359 Definitions: []clusterv1.PatchDefinition{ 1360 { 1361 Selector: clusterv1.PatchSelector{ 1362 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 1363 Kind: "ControlPlaneTemplate", 1364 MatchResources: clusterv1.PatchSelectorMatch{ 1365 ControlPlane: true, 1366 }, 1367 }, 1368 JSONPatches: []clusterv1.JSONPatch{ 1369 { 1370 Op: "add", 1371 Path: "/spec/template/spec/", 1372 ValueFrom: &clusterv1.JSONPatchValue{ 1373 Variable: pointer.String("builtin.machineDeployment.version"), 1374 }, 1375 }, 1376 }, 1377 }, 1378 }, 1379 }, 1380 }, 1381 }, 1382 }, 1383 wantErr: false, 1384 }, 1385 1386 // Patch with External 1387 { 1388 name: "pass if patch defines both external.generateExtension and external.validateExtension", 1389 clusterClass: clusterv1.ClusterClass{ 1390 Spec: clusterv1.ClusterClassSpec{ 1391 ControlPlane: clusterv1.ControlPlaneClass{ 1392 LocalObjectTemplate: clusterv1.LocalObjectTemplate{ 1393 Ref: &corev1.ObjectReference{ 1394 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 1395 Kind: "ControlPlaneTemplate", 1396 }, 1397 }, 1398 }, 1399 1400 Patches: []clusterv1.ClusterClassPatch{ 1401 { 1402 Name: "patch1", 1403 External: &clusterv1.ExternalPatchDefinition{ 1404 GenerateExtension: pointer.String("generate-extension"), 1405 ValidateExtension: pointer.String("generate-extension"), 1406 }, 1407 }, 1408 }, 1409 }, 1410 }, 1411 runtimeSDK: true, 1412 wantErr: false, 1413 }, 1414 { 1415 name: "error if patch defines both external and RuntimeSDK is not enabled", 1416 clusterClass: clusterv1.ClusterClass{ 1417 Spec: clusterv1.ClusterClassSpec{ 1418 ControlPlane: clusterv1.ControlPlaneClass{ 1419 LocalObjectTemplate: clusterv1.LocalObjectTemplate{ 1420 Ref: &corev1.ObjectReference{ 1421 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 1422 Kind: "ControlPlaneTemplate", 1423 }, 1424 }, 1425 }, 1426 1427 Patches: []clusterv1.ClusterClassPatch{ 1428 { 1429 Name: "patch1", 1430 External: &clusterv1.ExternalPatchDefinition{ 1431 GenerateExtension: pointer.String("generate-extension"), 1432 ValidateExtension: pointer.String("generate-extension"), 1433 }, 1434 }, 1435 }, 1436 }, 1437 }, 1438 runtimeSDK: false, 1439 wantErr: true, 1440 }, 1441 { 1442 name: "error if patch defines neither external.generateExtension nor external.validateExtension", 1443 clusterClass: clusterv1.ClusterClass{ 1444 Spec: clusterv1.ClusterClassSpec{ 1445 ControlPlane: clusterv1.ControlPlaneClass{ 1446 LocalObjectTemplate: clusterv1.LocalObjectTemplate{ 1447 Ref: &corev1.ObjectReference{ 1448 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 1449 Kind: "ControlPlaneTemplate", 1450 }, 1451 }, 1452 }, 1453 1454 Patches: []clusterv1.ClusterClassPatch{ 1455 { 1456 Name: "patch1", 1457 External: &clusterv1.ExternalPatchDefinition{}, 1458 }, 1459 }, 1460 }, 1461 }, 1462 runtimeSDK: true, 1463 wantErr: true, 1464 }, 1465 { 1466 name: "error if patch defines both external and definitions", 1467 clusterClass: clusterv1.ClusterClass{ 1468 Spec: clusterv1.ClusterClassSpec{ 1469 ControlPlane: clusterv1.ControlPlaneClass{ 1470 LocalObjectTemplate: clusterv1.LocalObjectTemplate{ 1471 Ref: &corev1.ObjectReference{ 1472 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 1473 Kind: "ControlPlaneTemplate", 1474 }, 1475 }, 1476 }, 1477 1478 Patches: []clusterv1.ClusterClassPatch{ 1479 { 1480 Name: "patch1", 1481 External: &clusterv1.ExternalPatchDefinition{ 1482 GenerateExtension: pointer.String("generate-extension"), 1483 ValidateExtension: pointer.String("generate-extension"), 1484 }, 1485 Definitions: []clusterv1.PatchDefinition{}, 1486 }, 1487 }, 1488 }, 1489 }, 1490 runtimeSDK: true, 1491 wantErr: true, 1492 }, 1493 { 1494 name: "error if neither external nor definitions is defined", 1495 clusterClass: clusterv1.ClusterClass{ 1496 Spec: clusterv1.ClusterClassSpec{ 1497 ControlPlane: clusterv1.ControlPlaneClass{ 1498 LocalObjectTemplate: clusterv1.LocalObjectTemplate{ 1499 Ref: &corev1.ObjectReference{ 1500 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 1501 Kind: "ControlPlaneTemplate", 1502 }, 1503 }, 1504 }, 1505 1506 Patches: []clusterv1.ClusterClassPatch{ 1507 { 1508 Name: "patch1", 1509 }, 1510 }, 1511 }, 1512 }, 1513 runtimeSDK: true, 1514 wantErr: true, 1515 }, 1516 } 1517 for i := range tests { 1518 tt := tests[i] 1519 t.Run(tt.name, func(t *testing.T) { 1520 defer utilfeature.SetFeatureGateDuringTest(t, feature.Gates, feature.RuntimeSDK, tt.runtimeSDK)() 1521 1522 g := NewWithT(t) 1523 1524 errList := validatePatches(&tt.clusterClass) 1525 if tt.wantErr { 1526 g.Expect(errList).NotTo(BeEmpty()) 1527 return 1528 } 1529 g.Expect(errList).To(BeEmpty()) 1530 }) 1531 } 1532 } 1533 1534 func Test_validateSelectors(t *testing.T) { 1535 tests := []struct { 1536 name string 1537 selector clusterv1.PatchSelector 1538 clusterClass *clusterv1.ClusterClass 1539 wantErr bool 1540 }{ 1541 { 1542 name: "error if selectors are all set to false or empty", 1543 selector: clusterv1.PatchSelector{ 1544 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1545 Kind: "InfrastructureClusterTemplate", 1546 MatchResources: clusterv1.PatchSelectorMatch{ 1547 ControlPlane: false, 1548 InfrastructureCluster: false, 1549 MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{}, 1550 }, 1551 }, 1552 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1553 WithControlPlaneTemplate( 1554 refToUnstructured( 1555 &corev1.ObjectReference{ 1556 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1557 Kind: "InfrastructureClusterTemplate", 1558 }), 1559 ). 1560 Build(), 1561 wantErr: true, 1562 }, 1563 { 1564 name: "pass if selector targets an existing infrastructureCluster reference", 1565 selector: clusterv1.PatchSelector{ 1566 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1567 Kind: "InfrastructureClusterTemplate", 1568 MatchResources: clusterv1.PatchSelectorMatch{ 1569 InfrastructureCluster: true, 1570 }, 1571 }, 1572 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1573 WithInfrastructureClusterTemplate( 1574 refToUnstructured( 1575 &corev1.ObjectReference{ 1576 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1577 Kind: "InfrastructureClusterTemplate", 1578 }), 1579 ). 1580 Build(), 1581 }, 1582 { 1583 name: "error if selector targets a non-existing infrastructureCluster APIVersion reference", 1584 selector: clusterv1.PatchSelector{ 1585 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1586 Kind: "InfrastructureClusterTemplate", 1587 MatchResources: clusterv1.PatchSelectorMatch{ 1588 InfrastructureCluster: true, 1589 }, 1590 }, 1591 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1592 WithInfrastructureClusterTemplate( 1593 refToUnstructured( 1594 &corev1.ObjectReference{ 1595 APIVersion: "nonmatchinginfrastructure.cluster.x-k8s.io/v1beta1", 1596 Kind: "InfrastructureClusterTemplate", 1597 }), 1598 ). 1599 Build(), 1600 wantErr: true, 1601 }, 1602 { 1603 name: "pass if selector targets an existing controlPlane reference", 1604 selector: clusterv1.PatchSelector{ 1605 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 1606 Kind: "ControlPlaneTemplate", 1607 MatchResources: clusterv1.PatchSelectorMatch{ 1608 ControlPlane: true, 1609 }, 1610 }, 1611 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1612 WithControlPlaneTemplate( 1613 refToUnstructured( 1614 &corev1.ObjectReference{ 1615 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 1616 Kind: "ControlPlaneTemplate", 1617 }), 1618 ). 1619 Build(), 1620 }, 1621 { 1622 name: "error if selector targets a non-existing controlPlane Kind reference", 1623 selector: clusterv1.PatchSelector{ 1624 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 1625 Kind: "ControlPlaneTemplate", 1626 MatchResources: clusterv1.PatchSelectorMatch{ 1627 ControlPlane: true, 1628 }, 1629 }, 1630 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1631 WithControlPlaneTemplate( 1632 refToUnstructured( 1633 &corev1.ObjectReference{ 1634 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 1635 Kind: "NonMatchingControlPlaneTemplate", 1636 }), 1637 ). 1638 Build(), 1639 wantErr: true, 1640 }, 1641 { 1642 name: "pass if selector targets an existing controlPlane machineInfrastructure reference", 1643 selector: clusterv1.PatchSelector{ 1644 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1645 Kind: "InfrastructureMachineTemplate", 1646 MatchResources: clusterv1.PatchSelectorMatch{ 1647 ControlPlane: true, 1648 }, 1649 }, 1650 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1651 WithControlPlaneTemplate( 1652 refToUnstructured( 1653 &corev1.ObjectReference{ 1654 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 1655 Kind: "NonMatchingControlPlaneTemplate", 1656 }), 1657 ). 1658 WithControlPlaneInfrastructureMachineTemplate( 1659 refToUnstructured( 1660 &corev1.ObjectReference{ 1661 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1662 Kind: "InfrastructureMachineTemplate", 1663 }), 1664 ). 1665 Build(), 1666 }, 1667 { 1668 name: "error if selector targets a non-existing controlPlane machineInfrastructure reference", 1669 selector: clusterv1.PatchSelector{ 1670 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1671 Kind: "InfrastructureMachineTemplate", 1672 MatchResources: clusterv1.PatchSelectorMatch{ 1673 ControlPlane: true, 1674 }, 1675 }, 1676 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1677 WithControlPlaneTemplate( 1678 refToUnstructured( 1679 &corev1.ObjectReference{ 1680 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 1681 Kind: "NonMatchingControlPlaneTemplate", 1682 }), 1683 ). 1684 WithControlPlaneInfrastructureMachineTemplate( 1685 refToUnstructured( 1686 &corev1.ObjectReference{ 1687 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1688 Kind: "NonMatchingInfrastructureMachineTemplate", 1689 }), 1690 ). 1691 Build(), 1692 wantErr: true, 1693 }, 1694 { 1695 name: "pass if selector targets an existing MachineDeploymentClass BootstrapTemplate", 1696 selector: clusterv1.PatchSelector{ 1697 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1698 Kind: "BootstrapTemplate", 1699 MatchResources: clusterv1.PatchSelectorMatch{ 1700 MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{ 1701 Names: []string{"aa"}, 1702 }, 1703 }, 1704 }, 1705 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1706 WithWorkerMachineDeploymentClasses( 1707 *builder.MachineDeploymentClass("aa"). 1708 WithInfrastructureTemplate( 1709 refToUnstructured(&corev1.ObjectReference{ 1710 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1711 Kind: "InfrastructureMachineTemplate", 1712 })). 1713 WithBootstrapTemplate( 1714 refToUnstructured(&corev1.ObjectReference{ 1715 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1716 Kind: "BootstrapTemplate", 1717 })). 1718 Build(), 1719 ). 1720 Build(), 1721 }, 1722 { 1723 name: "pass if selector targets an existing MachineDeploymentClass InfrastructureTemplate", 1724 selector: clusterv1.PatchSelector{ 1725 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1726 Kind: "InfrastructureMachineTemplate", 1727 MatchResources: clusterv1.PatchSelectorMatch{ 1728 MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{ 1729 Names: []string{"aa"}, 1730 }, 1731 }, 1732 }, 1733 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1734 WithWorkerMachineDeploymentClasses( 1735 *builder.MachineDeploymentClass("aa"). 1736 WithInfrastructureTemplate( 1737 refToUnstructured(&corev1.ObjectReference{ 1738 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1739 Kind: "InfrastructureMachineTemplate", 1740 })). 1741 WithBootstrapTemplate( 1742 refToUnstructured(&corev1.ObjectReference{ 1743 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1744 Kind: "BootstrapTemplate", 1745 })). 1746 Build(), 1747 ). 1748 Build(), 1749 }, 1750 { 1751 name: "error if selector targets a non-existing MachineDeploymentClass InfrastructureTemplate", 1752 selector: clusterv1.PatchSelector{ 1753 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1754 Kind: "InfrastructureMachineTemplate", 1755 MatchResources: clusterv1.PatchSelectorMatch{ 1756 MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{ 1757 Names: []string{"bb"}, 1758 }, 1759 }, 1760 }, 1761 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1762 WithWorkerMachineDeploymentClasses( 1763 *builder.MachineDeploymentClass("aa"). 1764 WithInfrastructureTemplate( 1765 refToUnstructured(&corev1.ObjectReference{ 1766 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1767 Kind: "InfrastructureMachineTemplate", 1768 })). 1769 WithBootstrapTemplate( 1770 refToUnstructured(&corev1.ObjectReference{ 1771 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1772 Kind: "BootstrapTemplate", 1773 })). 1774 Build(), 1775 *builder.MachineDeploymentClass("bb"). 1776 WithInfrastructureTemplate( 1777 refToUnstructured(&corev1.ObjectReference{ 1778 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1779 Kind: "NonMatchingInfrastructureMachineTemplate", 1780 })). 1781 WithBootstrapTemplate( 1782 refToUnstructured(&corev1.ObjectReference{ 1783 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1784 Kind: "BootstrapTemplate", 1785 })). 1786 Build(), 1787 ). 1788 Build(), 1789 wantErr: true, 1790 }, 1791 { 1792 name: "fail if selector targets ControlPlane Machine Infrastructure but does not have MatchResources.ControlPlane enabled", 1793 selector: clusterv1.PatchSelector{ 1794 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1795 Kind: "InfrastructureMachineTemplate", 1796 MatchResources: clusterv1.PatchSelectorMatch{ 1797 MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{ 1798 Names: []string{"bb"}, 1799 }, 1800 ControlPlane: false, 1801 }, 1802 }, 1803 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1804 WithControlPlaneInfrastructureMachineTemplate( 1805 refToUnstructured( 1806 &corev1.ObjectReference{ 1807 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1808 Kind: "InfrastructureMachineTemplate", 1809 }), 1810 ). 1811 Build(), 1812 wantErr: true, 1813 }, 1814 { 1815 name: "error if selector targets an empty MachineDeploymentClass InfrastructureTemplate", 1816 selector: clusterv1.PatchSelector{ 1817 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1818 Kind: "InfrastructureMachineTemplate", 1819 MatchResources: clusterv1.PatchSelectorMatch{}, 1820 }, 1821 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1822 WithWorkerMachineDeploymentClasses( 1823 *builder.MachineDeploymentClass("aa"). 1824 WithInfrastructureTemplate( 1825 refToUnstructured(&corev1.ObjectReference{ 1826 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1827 Kind: "InfrastructureMachineTemplate", 1828 })). 1829 WithBootstrapTemplate( 1830 refToUnstructured(&corev1.ObjectReference{ 1831 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1832 Kind: "BootstrapTemplate", 1833 })). 1834 Build(), 1835 *builder.MachineDeploymentClass("bb"). 1836 WithInfrastructureTemplate( 1837 refToUnstructured(&corev1.ObjectReference{ 1838 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1839 Kind: "NonMatchingInfrastructureMachineTemplate", 1840 })). 1841 WithBootstrapTemplate( 1842 refToUnstructured(&corev1.ObjectReference{ 1843 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1844 Kind: "BootstrapTemplate", 1845 })). 1846 Build(), 1847 ). 1848 Build(), 1849 wantErr: true, 1850 }, 1851 { 1852 name: "error if selector targets a bad pattern for matching MachineDeploymentClass InfrastructureTemplate", 1853 selector: clusterv1.PatchSelector{ 1854 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1855 Kind: "InfrastructureMachineTemplate", 1856 MatchResources: clusterv1.PatchSelectorMatch{ 1857 MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{ 1858 Names: []string{"a*a"}, 1859 }, 1860 }, 1861 }, 1862 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1863 WithWorkerMachineDeploymentClasses( 1864 *builder.MachineDeploymentClass("a-something-a"). 1865 WithInfrastructureTemplate( 1866 refToUnstructured(&corev1.ObjectReference{ 1867 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1868 Kind: "InfrastructureMachineTemplate", 1869 })). 1870 WithBootstrapTemplate( 1871 refToUnstructured(&corev1.ObjectReference{ 1872 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1873 Kind: "BootstrapTemplate", 1874 })). 1875 Build(), 1876 ). 1877 Build(), 1878 wantErr: true, 1879 }, 1880 { 1881 name: "pass if selector targets an existing MachineDeploymentClass InfrastructureTemplate with prefix *", 1882 selector: clusterv1.PatchSelector{ 1883 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1884 Kind: "InfrastructureMachineTemplate", 1885 MatchResources: clusterv1.PatchSelectorMatch{ 1886 MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{ 1887 Names: []string{"a-*"}, 1888 }, 1889 }, 1890 }, 1891 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1892 WithWorkerMachineDeploymentClasses( 1893 *builder.MachineDeploymentClass("a-something-a"). 1894 WithInfrastructureTemplate( 1895 refToUnstructured(&corev1.ObjectReference{ 1896 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1897 Kind: "InfrastructureMachineTemplate", 1898 })). 1899 WithBootstrapTemplate( 1900 refToUnstructured(&corev1.ObjectReference{ 1901 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1902 Kind: "BootstrapTemplate", 1903 })). 1904 Build(), 1905 ). 1906 Build(), 1907 }, 1908 { 1909 name: "pass if selector targets an existing MachineDeploymentClass InfrastructureTemplate with suffix *", 1910 selector: clusterv1.PatchSelector{ 1911 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1912 Kind: "InfrastructureMachineTemplate", 1913 MatchResources: clusterv1.PatchSelectorMatch{ 1914 MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{ 1915 Names: []string{"*-a"}, 1916 }, 1917 }, 1918 }, 1919 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1920 WithWorkerMachineDeploymentClasses( 1921 *builder.MachineDeploymentClass("a-something-a"). 1922 WithInfrastructureTemplate( 1923 refToUnstructured(&corev1.ObjectReference{ 1924 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1925 Kind: "InfrastructureMachineTemplate", 1926 })). 1927 WithBootstrapTemplate( 1928 refToUnstructured(&corev1.ObjectReference{ 1929 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1930 Kind: "BootstrapTemplate", 1931 })). 1932 Build(), 1933 ). 1934 Build(), 1935 }, 1936 { 1937 name: "pass if selector targets all existing MachineDeploymentClass InfrastructureTemplate with *", 1938 selector: clusterv1.PatchSelector{ 1939 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1940 Kind: "InfrastructureMachineTemplate", 1941 MatchResources: clusterv1.PatchSelectorMatch{ 1942 MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{ 1943 Names: []string{"*"}, 1944 }, 1945 }, 1946 }, 1947 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1948 WithWorkerMachineDeploymentClasses( 1949 *builder.MachineDeploymentClass("a-something-a"). 1950 WithInfrastructureTemplate( 1951 refToUnstructured(&corev1.ObjectReference{ 1952 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1953 Kind: "InfrastructureMachineTemplate", 1954 })). 1955 WithBootstrapTemplate( 1956 refToUnstructured(&corev1.ObjectReference{ 1957 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1958 Kind: "BootstrapTemplate", 1959 })). 1960 Build(), 1961 ). 1962 Build(), 1963 }, 1964 // The following tests have selectors which match multiple resources at the same time. 1965 { 1966 name: "fail if selector targets a matching infrastructureCluster reference and a not matching control plane", 1967 selector: clusterv1.PatchSelector{ 1968 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1969 Kind: "InfrastructureClusterTemplate", 1970 MatchResources: clusterv1.PatchSelectorMatch{ 1971 InfrastructureCluster: true, 1972 ControlPlane: true, 1973 }, 1974 }, 1975 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1976 WithInfrastructureClusterTemplate( 1977 refToUnstructured( 1978 &corev1.ObjectReference{ 1979 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1980 Kind: "InfrastructureClusterTemplate", 1981 }), 1982 ). 1983 WithControlPlaneTemplate( 1984 refToUnstructured( 1985 &corev1.ObjectReference{ 1986 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 1987 Kind: "NonMatchingControlPlaneTemplate", 1988 }), 1989 ). 1990 WithControlPlaneInfrastructureMachineTemplate( 1991 refToUnstructured( 1992 &corev1.ObjectReference{ 1993 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1994 Kind: "NonMatchingInfrastructureMachineTemplate", 1995 }), 1996 ). 1997 Build(), 1998 wantErr: true, 1999 }, 2000 { 2001 name: "pass if selector targets BOTH an existing ControlPlane MachineInfrastructureTemplate and an existing MachineDeploymentClass InfrastructureTemplate", 2002 selector: clusterv1.PatchSelector{ 2003 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2004 Kind: "InfrastructureMachineTemplate", 2005 MatchResources: clusterv1.PatchSelectorMatch{ 2006 MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{ 2007 Names: []string{"bb"}, 2008 }, 2009 ControlPlane: true, 2010 }, 2011 }, 2012 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 2013 WithControlPlaneInfrastructureMachineTemplate( 2014 refToUnstructured( 2015 &corev1.ObjectReference{ 2016 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2017 Kind: "InfrastructureMachineTemplate", 2018 }), 2019 ). 2020 WithWorkerMachineDeploymentClasses( 2021 *builder.MachineDeploymentClass("aa"). 2022 WithInfrastructureTemplate( 2023 refToUnstructured(&corev1.ObjectReference{ 2024 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2025 Kind: "InfrastructureMachineTemplate", 2026 })). 2027 WithBootstrapTemplate( 2028 refToUnstructured(&corev1.ObjectReference{ 2029 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 2030 Kind: "BootstrapTemplate", 2031 })). 2032 Build(), 2033 *builder.MachineDeploymentClass("bb"). 2034 WithInfrastructureTemplate( 2035 refToUnstructured(&corev1.ObjectReference{ 2036 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2037 Kind: "InfrastructureMachineTemplate", 2038 })). 2039 WithBootstrapTemplate( 2040 refToUnstructured(&corev1.ObjectReference{ 2041 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 2042 Kind: "BootstrapTemplate", 2043 })). 2044 Build(), 2045 ). 2046 Build(), 2047 wantErr: false, 2048 }, 2049 { 2050 name: "fail if selector targets BOTH an existing ControlPlane MachineInfrastructureTemplate and an existing MachineDeploymentClass InfrastructureTemplate but does not match all MachineDeployment classes", 2051 selector: clusterv1.PatchSelector{ 2052 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2053 Kind: "InfrastructureMachineTemplate", 2054 MatchResources: clusterv1.PatchSelectorMatch{ 2055 MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{ 2056 Names: []string{"aa", "bb"}, 2057 }, 2058 ControlPlane: true, 2059 }, 2060 }, 2061 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 2062 WithControlPlaneTemplate( 2063 refToUnstructured( 2064 &corev1.ObjectReference{ 2065 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 2066 Kind: "NonMatchingControlPlaneTemplate", 2067 }), 2068 ). 2069 WithControlPlaneInfrastructureMachineTemplate( 2070 refToUnstructured( 2071 &corev1.ObjectReference{ 2072 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2073 Kind: "InfrastructureMachineTemplate", 2074 }), 2075 ). 2076 WithWorkerMachineDeploymentClasses( 2077 *builder.MachineDeploymentClass("aa"). 2078 WithInfrastructureTemplate( 2079 refToUnstructured(&corev1.ObjectReference{ 2080 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2081 Kind: "NonMatchingInfrastructureMachineTemplate", 2082 })). 2083 WithBootstrapTemplate( 2084 refToUnstructured(&corev1.ObjectReference{ 2085 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 2086 Kind: "BootstrapTemplate", 2087 })). 2088 Build(), 2089 *builder.MachineDeploymentClass("bb"). 2090 WithInfrastructureTemplate( 2091 refToUnstructured(&corev1.ObjectReference{ 2092 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2093 Kind: "InfrastructureMachineTemplate", 2094 })). 2095 WithBootstrapTemplate( 2096 refToUnstructured(&corev1.ObjectReference{ 2097 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 2098 Kind: "BootstrapTemplate", 2099 })). 2100 Build(), 2101 ). 2102 Build(), 2103 wantErr: true, 2104 }, 2105 { 2106 name: "fail if selector targets BOTH an existing ControlPlane MachineInfrastructureTemplate and an existing MachineDeploymentClass InfrastructureTemplate but matches only one", 2107 selector: clusterv1.PatchSelector{ 2108 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2109 Kind: "InfrastructureMachineTemplate", 2110 MatchResources: clusterv1.PatchSelectorMatch{ 2111 MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{ 2112 Names: []string{"bb"}, 2113 }, 2114 ControlPlane: true, 2115 }, 2116 }, 2117 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 2118 WithControlPlaneTemplate( 2119 refToUnstructured( 2120 &corev1.ObjectReference{ 2121 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 2122 Kind: "NonMatchingControlPlaneTemplate", 2123 }), 2124 ). 2125 WithControlPlaneInfrastructureMachineTemplate( 2126 refToUnstructured( 2127 &corev1.ObjectReference{ 2128 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2129 Kind: "InfrastructureMachineTemplate", 2130 }), 2131 ). 2132 WithWorkerMachineDeploymentClasses( 2133 *builder.MachineDeploymentClass("bb"). 2134 WithInfrastructureTemplate( 2135 refToUnstructured(&corev1.ObjectReference{ 2136 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2137 Kind: "OtherInfrastructureMachineTemplate", 2138 })). 2139 WithBootstrapTemplate( 2140 refToUnstructured(&corev1.ObjectReference{ 2141 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 2142 Kind: "BootstrapTemplate", 2143 })). 2144 Build(), 2145 ). 2146 Build(), 2147 wantErr: true, 2148 }, 2149 { 2150 name: "fail if selector targets everything but nothing matches", 2151 selector: clusterv1.PatchSelector{ 2152 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2153 Kind: "NotMatchingInfrastructureMachineTemplate", 2154 MatchResources: clusterv1.PatchSelectorMatch{ 2155 ControlPlane: true, 2156 InfrastructureCluster: true, 2157 MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{ 2158 Names: []string{"bb"}, 2159 }, 2160 }, 2161 }, 2162 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 2163 WithInfrastructureClusterTemplate( 2164 refToUnstructured( 2165 &corev1.ObjectReference{ 2166 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2167 Kind: "InfrastructureClusterTemplate", 2168 }), 2169 ). 2170 WithControlPlaneTemplate( 2171 refToUnstructured( 2172 &corev1.ObjectReference{ 2173 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 2174 Kind: "NonMatchingControlPlaneTemplate", 2175 }), 2176 ). 2177 WithControlPlaneInfrastructureMachineTemplate( 2178 refToUnstructured( 2179 &corev1.ObjectReference{ 2180 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2181 Kind: "InfrastructureMachineTemplate", 2182 }), 2183 ). 2184 WithWorkerMachineDeploymentClasses( 2185 *builder.MachineDeploymentClass("bb"). 2186 WithInfrastructureTemplate( 2187 refToUnstructured(&corev1.ObjectReference{ 2188 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2189 Kind: "OtherInfrastructureMachineTemplate", 2190 })). 2191 WithBootstrapTemplate( 2192 refToUnstructured(&corev1.ObjectReference{ 2193 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 2194 Kind: "BootstrapTemplate", 2195 })). 2196 Build(), 2197 ). 2198 Build(), 2199 wantErr: true, 2200 }, 2201 } 2202 for _, tt := range tests { 2203 t.Run(tt.name, func(t *testing.T) { 2204 g := NewWithT(t) 2205 2206 err := validateSelectors(tt.selector, tt.clusterClass, field.NewPath("")) 2207 2208 if tt.wantErr { 2209 g.Expect(err.ToAggregate()).To(HaveOccurred()) 2210 return 2211 } 2212 g.Expect(err.ToAggregate()).ToNot(HaveOccurred()) 2213 }) 2214 } 2215 } 2216 2217 func TestGetVariableName(t *testing.T) { 2218 tests := []struct { 2219 name string 2220 variable string 2221 variableName string 2222 }{ 2223 { 2224 name: "simple variable", 2225 variable: "variableA", 2226 variableName: "variableA", 2227 }, 2228 { 2229 name: "variable object", 2230 variable: "variableObject.field", 2231 variableName: "variableObject", 2232 }, 2233 { 2234 name: "variable array", 2235 variable: "variableArray[0]", 2236 variableName: "variableArray", 2237 }, 2238 } 2239 2240 for _, tt := range tests { 2241 t.Run(tt.name, func(t *testing.T) { 2242 g := NewWithT(t) 2243 2244 g.Expect(getVariableName(tt.variable)).To(Equal(tt.variableName)) 2245 }) 2246 } 2247 }