sigs.k8s.io/cluster-api@v1.7.1/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/ptr" 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: ptr.To("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: ptr.To("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: ptr.To("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: ptr.To("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: ptr.To("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: ptr.To(`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: ptr.To(`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: ptr.To("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: ptr.To("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: ptr.To("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: ptr.To("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: ptr.To("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: ptr.To("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: ptr.To("variableName"), 1008 Template: ptr.To(`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: ptr.To(`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: ptr.To(`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: ptr.To("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: ptr.To("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: ptr.To("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: ptr.To("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: ptr.To("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: ptr.To("generate-extension"), 1405 ValidateExtension: ptr.To("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: ptr.To("generate-extension"), 1432 ValidateExtension: ptr.To("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: ptr.To("generate-extension"), 1483 ValidateExtension: ptr.To("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 MachinePoolClass: &clusterv1.PatchSelectorMatchMachinePoolClass{}, 1551 }, 1552 }, 1553 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1554 WithControlPlaneTemplate( 1555 refToUnstructured( 1556 &corev1.ObjectReference{ 1557 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1558 Kind: "InfrastructureClusterTemplate", 1559 }), 1560 ). 1561 Build(), 1562 wantErr: true, 1563 }, 1564 { 1565 name: "pass if selector targets an existing infrastructureCluster reference", 1566 selector: clusterv1.PatchSelector{ 1567 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1568 Kind: "InfrastructureClusterTemplate", 1569 MatchResources: clusterv1.PatchSelectorMatch{ 1570 InfrastructureCluster: true, 1571 }, 1572 }, 1573 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1574 WithInfrastructureClusterTemplate( 1575 refToUnstructured( 1576 &corev1.ObjectReference{ 1577 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1578 Kind: "InfrastructureClusterTemplate", 1579 }), 1580 ). 1581 Build(), 1582 }, 1583 { 1584 name: "error if selector targets a non-existing infrastructureCluster APIVersion reference", 1585 selector: clusterv1.PatchSelector{ 1586 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1587 Kind: "InfrastructureClusterTemplate", 1588 MatchResources: clusterv1.PatchSelectorMatch{ 1589 InfrastructureCluster: true, 1590 }, 1591 }, 1592 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1593 WithInfrastructureClusterTemplate( 1594 refToUnstructured( 1595 &corev1.ObjectReference{ 1596 APIVersion: "nonmatchinginfrastructure.cluster.x-k8s.io/v1beta1", 1597 Kind: "InfrastructureClusterTemplate", 1598 }), 1599 ). 1600 Build(), 1601 wantErr: true, 1602 }, 1603 { 1604 name: "pass if selector targets an existing controlPlane reference", 1605 selector: clusterv1.PatchSelector{ 1606 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 1607 Kind: "ControlPlaneTemplate", 1608 MatchResources: clusterv1.PatchSelectorMatch{ 1609 ControlPlane: true, 1610 }, 1611 }, 1612 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1613 WithControlPlaneTemplate( 1614 refToUnstructured( 1615 &corev1.ObjectReference{ 1616 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 1617 Kind: "ControlPlaneTemplate", 1618 }), 1619 ). 1620 Build(), 1621 }, 1622 { 1623 name: "error if selector targets a non-existing controlPlane Kind reference", 1624 selector: clusterv1.PatchSelector{ 1625 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 1626 Kind: "ControlPlaneTemplate", 1627 MatchResources: clusterv1.PatchSelectorMatch{ 1628 ControlPlane: true, 1629 }, 1630 }, 1631 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1632 WithControlPlaneTemplate( 1633 refToUnstructured( 1634 &corev1.ObjectReference{ 1635 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 1636 Kind: "NonMatchingControlPlaneTemplate", 1637 }), 1638 ). 1639 Build(), 1640 wantErr: true, 1641 }, 1642 { 1643 name: "pass if selector targets an existing controlPlane machineInfrastructure reference", 1644 selector: clusterv1.PatchSelector{ 1645 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1646 Kind: "InfrastructureMachineTemplate", 1647 MatchResources: clusterv1.PatchSelectorMatch{ 1648 ControlPlane: true, 1649 }, 1650 }, 1651 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1652 WithControlPlaneTemplate( 1653 refToUnstructured( 1654 &corev1.ObjectReference{ 1655 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 1656 Kind: "NonMatchingControlPlaneTemplate", 1657 }), 1658 ). 1659 WithControlPlaneInfrastructureMachineTemplate( 1660 refToUnstructured( 1661 &corev1.ObjectReference{ 1662 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1663 Kind: "InfrastructureMachineTemplate", 1664 }), 1665 ). 1666 Build(), 1667 }, 1668 { 1669 name: "error if selector targets a non-existing controlPlane machineInfrastructure reference", 1670 selector: clusterv1.PatchSelector{ 1671 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1672 Kind: "InfrastructureMachineTemplate", 1673 MatchResources: clusterv1.PatchSelectorMatch{ 1674 ControlPlane: true, 1675 }, 1676 }, 1677 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1678 WithControlPlaneTemplate( 1679 refToUnstructured( 1680 &corev1.ObjectReference{ 1681 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 1682 Kind: "NonMatchingControlPlaneTemplate", 1683 }), 1684 ). 1685 WithControlPlaneInfrastructureMachineTemplate( 1686 refToUnstructured( 1687 &corev1.ObjectReference{ 1688 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1689 Kind: "NonMatchingInfrastructureMachineTemplate", 1690 }), 1691 ). 1692 Build(), 1693 wantErr: true, 1694 }, 1695 { 1696 name: "pass if selector targets an existing MachineDeploymentClass and MachinePoolClass BootstrapTemplate", 1697 selector: clusterv1.PatchSelector{ 1698 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1699 Kind: "BootstrapTemplate", 1700 MatchResources: clusterv1.PatchSelectorMatch{ 1701 MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{ 1702 Names: []string{"aa"}, 1703 }, 1704 MachinePoolClass: &clusterv1.PatchSelectorMatchMachinePoolClass{ 1705 Names: []string{"aa"}, 1706 }, 1707 }, 1708 }, 1709 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1710 WithWorkerMachineDeploymentClasses( 1711 *builder.MachineDeploymentClass("aa"). 1712 WithInfrastructureTemplate( 1713 refToUnstructured(&corev1.ObjectReference{ 1714 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1715 Kind: "InfrastructureMachineTemplate", 1716 })). 1717 WithBootstrapTemplate( 1718 refToUnstructured(&corev1.ObjectReference{ 1719 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1720 Kind: "BootstrapTemplate", 1721 })). 1722 Build(), 1723 ). 1724 WithWorkerMachinePoolClasses( 1725 *builder.MachinePoolClass("aa"). 1726 WithInfrastructureTemplate( 1727 refToUnstructured(&corev1.ObjectReference{ 1728 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1729 Kind: "InfrastructureMachinePoolTemplate", 1730 })). 1731 WithBootstrapTemplate( 1732 refToUnstructured(&corev1.ObjectReference{ 1733 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1734 Kind: "BootstrapTemplate", 1735 })). 1736 Build(), 1737 ). 1738 Build(), 1739 }, 1740 { 1741 name: "pass if selector targets an existing MachineDeploymentClass InfrastructureTemplate", 1742 selector: clusterv1.PatchSelector{ 1743 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1744 Kind: "InfrastructureMachineTemplate", 1745 MatchResources: clusterv1.PatchSelectorMatch{ 1746 MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{ 1747 Names: []string{"aa"}, 1748 }, 1749 }, 1750 }, 1751 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1752 WithWorkerMachineDeploymentClasses( 1753 *builder.MachineDeploymentClass("aa"). 1754 WithInfrastructureTemplate( 1755 refToUnstructured(&corev1.ObjectReference{ 1756 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1757 Kind: "InfrastructureMachineTemplate", 1758 })). 1759 WithBootstrapTemplate( 1760 refToUnstructured(&corev1.ObjectReference{ 1761 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1762 Kind: "BootstrapTemplate", 1763 })). 1764 Build(), 1765 ). 1766 Build(), 1767 }, 1768 { 1769 name: "pass if selector targets an existing MachinePoolClass InfrastructureTemplate", 1770 selector: clusterv1.PatchSelector{ 1771 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1772 Kind: "InfrastructureMachinePoolTemplate", 1773 MatchResources: clusterv1.PatchSelectorMatch{ 1774 MachinePoolClass: &clusterv1.PatchSelectorMatchMachinePoolClass{ 1775 Names: []string{"aa"}, 1776 }, 1777 }, 1778 }, 1779 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1780 WithWorkerMachinePoolClasses( 1781 *builder.MachinePoolClass("aa"). 1782 WithInfrastructureTemplate( 1783 refToUnstructured(&corev1.ObjectReference{ 1784 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1785 Kind: "InfrastructureMachinePoolTemplate", 1786 })). 1787 WithBootstrapTemplate( 1788 refToUnstructured(&corev1.ObjectReference{ 1789 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1790 Kind: "BootstrapTemplate", 1791 })). 1792 Build(), 1793 ). 1794 Build(), 1795 }, 1796 { 1797 name: "error if selector targets a non-existing MachineDeploymentClass InfrastructureTemplate", 1798 selector: clusterv1.PatchSelector{ 1799 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1800 Kind: "InfrastructureMachineTemplate", 1801 MatchResources: clusterv1.PatchSelectorMatch{ 1802 MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{ 1803 Names: []string{"bb"}, 1804 }, 1805 }, 1806 }, 1807 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1808 WithWorkerMachineDeploymentClasses( 1809 *builder.MachineDeploymentClass("aa"). 1810 WithInfrastructureTemplate( 1811 refToUnstructured(&corev1.ObjectReference{ 1812 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1813 Kind: "InfrastructureMachineTemplate", 1814 })). 1815 WithBootstrapTemplate( 1816 refToUnstructured(&corev1.ObjectReference{ 1817 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1818 Kind: "BootstrapTemplate", 1819 })). 1820 Build(), 1821 *builder.MachineDeploymentClass("bb"). 1822 WithInfrastructureTemplate( 1823 refToUnstructured(&corev1.ObjectReference{ 1824 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1825 Kind: "NonMatchingInfrastructureMachineTemplate", 1826 })). 1827 WithBootstrapTemplate( 1828 refToUnstructured(&corev1.ObjectReference{ 1829 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1830 Kind: "BootstrapTemplate", 1831 })). 1832 Build(), 1833 ). 1834 Build(), 1835 wantErr: true, 1836 }, 1837 { 1838 name: "error if selector targets a non-existing MachinePoolClass InfrastructureTemplate", 1839 selector: clusterv1.PatchSelector{ 1840 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1841 Kind: "InfrastructureMachinePoolTemplate", 1842 MatchResources: clusterv1.PatchSelectorMatch{ 1843 MachinePoolClass: &clusterv1.PatchSelectorMatchMachinePoolClass{ 1844 Names: []string{"bb"}, 1845 }, 1846 }, 1847 }, 1848 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1849 WithWorkerMachinePoolClasses( 1850 *builder.MachinePoolClass("aa"). 1851 WithInfrastructureTemplate( 1852 refToUnstructured(&corev1.ObjectReference{ 1853 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1854 Kind: "InfrastructureMachinePoolTemplate", 1855 })). 1856 WithBootstrapTemplate( 1857 refToUnstructured(&corev1.ObjectReference{ 1858 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1859 Kind: "BootstrapTemplate", 1860 })). 1861 Build(), 1862 *builder.MachinePoolClass("bb"). 1863 WithInfrastructureTemplate( 1864 refToUnstructured(&corev1.ObjectReference{ 1865 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1866 Kind: "NonMatchingInfrastructureMachinePoolTemplate", 1867 })). 1868 WithBootstrapTemplate( 1869 refToUnstructured(&corev1.ObjectReference{ 1870 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1871 Kind: "BootstrapTemplate", 1872 })). 1873 Build(), 1874 ). 1875 Build(), 1876 wantErr: true, 1877 }, 1878 { 1879 name: "fail if selector targets ControlPlane Machine Infrastructure but does not have MatchResources.ControlPlane enabled", 1880 selector: clusterv1.PatchSelector{ 1881 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1882 Kind: "InfrastructureMachineTemplate", 1883 MatchResources: clusterv1.PatchSelectorMatch{ 1884 MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{ 1885 Names: []string{"bb"}, 1886 }, 1887 ControlPlane: false, 1888 }, 1889 }, 1890 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1891 WithControlPlaneInfrastructureMachineTemplate( 1892 refToUnstructured( 1893 &corev1.ObjectReference{ 1894 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1895 Kind: "InfrastructureMachineTemplate", 1896 }), 1897 ). 1898 Build(), 1899 wantErr: true, 1900 }, 1901 { 1902 name: "error if selector targets an empty MachineDeploymentClass InfrastructureTemplate", 1903 selector: clusterv1.PatchSelector{ 1904 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1905 Kind: "InfrastructureMachineTemplate", 1906 MatchResources: clusterv1.PatchSelectorMatch{}, 1907 }, 1908 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1909 WithWorkerMachineDeploymentClasses( 1910 *builder.MachineDeploymentClass("aa"). 1911 WithInfrastructureTemplate( 1912 refToUnstructured(&corev1.ObjectReference{ 1913 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1914 Kind: "InfrastructureMachineTemplate", 1915 })). 1916 WithBootstrapTemplate( 1917 refToUnstructured(&corev1.ObjectReference{ 1918 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1919 Kind: "BootstrapTemplate", 1920 })). 1921 Build(), 1922 *builder.MachineDeploymentClass("bb"). 1923 WithInfrastructureTemplate( 1924 refToUnstructured(&corev1.ObjectReference{ 1925 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1926 Kind: "NonMatchingInfrastructureMachineTemplate", 1927 })). 1928 WithBootstrapTemplate( 1929 refToUnstructured(&corev1.ObjectReference{ 1930 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1931 Kind: "BootstrapTemplate", 1932 })). 1933 Build(), 1934 ). 1935 Build(), 1936 wantErr: true, 1937 }, 1938 { 1939 name: "error if selector targets an empty MachinePoolClass InfrastructureTemplate", 1940 selector: clusterv1.PatchSelector{ 1941 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1942 Kind: "InfrastructureMachinePoolTemplate", 1943 MatchResources: clusterv1.PatchSelectorMatch{}, 1944 }, 1945 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1946 WithWorkerMachinePoolClasses( 1947 *builder.MachinePoolClass("aa"). 1948 WithInfrastructureTemplate( 1949 refToUnstructured(&corev1.ObjectReference{ 1950 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1951 Kind: "InfrastructureMachinePoolTemplate", 1952 })). 1953 WithBootstrapTemplate( 1954 refToUnstructured(&corev1.ObjectReference{ 1955 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1956 Kind: "BootstrapTemplate", 1957 })). 1958 Build(), 1959 *builder.MachinePoolClass("bb"). 1960 WithInfrastructureTemplate( 1961 refToUnstructured(&corev1.ObjectReference{ 1962 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1963 Kind: "NonMatchingInfrastructureMachinePoolTemplate", 1964 })). 1965 WithBootstrapTemplate( 1966 refToUnstructured(&corev1.ObjectReference{ 1967 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1968 Kind: "BootstrapTemplate", 1969 })). 1970 Build(), 1971 ). 1972 Build(), 1973 wantErr: true, 1974 }, 1975 { 1976 name: "error if selector targets a bad pattern for matching MachineDeploymentClass InfrastructureTemplate", 1977 selector: clusterv1.PatchSelector{ 1978 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1979 Kind: "InfrastructureMachineTemplate", 1980 MatchResources: clusterv1.PatchSelectorMatch{ 1981 MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{ 1982 Names: []string{"a*a"}, 1983 }, 1984 }, 1985 }, 1986 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1987 WithWorkerMachineDeploymentClasses( 1988 *builder.MachineDeploymentClass("a-something-a"). 1989 WithInfrastructureTemplate( 1990 refToUnstructured(&corev1.ObjectReference{ 1991 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1992 Kind: "InfrastructureMachineTemplate", 1993 })). 1994 WithBootstrapTemplate( 1995 refToUnstructured(&corev1.ObjectReference{ 1996 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1997 Kind: "BootstrapTemplate", 1998 })). 1999 Build(), 2000 ). 2001 Build(), 2002 wantErr: true, 2003 }, 2004 { 2005 name: "error if selector targets a bad pattern for matching MachinePoolClass InfrastructureTemplate", 2006 selector: clusterv1.PatchSelector{ 2007 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2008 Kind: "InfrastructureMachinePoolTemplate", 2009 MatchResources: clusterv1.PatchSelectorMatch{ 2010 MachinePoolClass: &clusterv1.PatchSelectorMatchMachinePoolClass{ 2011 Names: []string{"a*a"}, 2012 }, 2013 }, 2014 }, 2015 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 2016 WithWorkerMachinePoolClasses( 2017 *builder.MachinePoolClass("a-something-a"). 2018 WithInfrastructureTemplate( 2019 refToUnstructured(&corev1.ObjectReference{ 2020 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2021 Kind: "InfrastructureMachinePoolTemplate", 2022 })). 2023 WithBootstrapTemplate( 2024 refToUnstructured(&corev1.ObjectReference{ 2025 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 2026 Kind: "BootstrapTemplate", 2027 })). 2028 Build(), 2029 ). 2030 Build(), 2031 wantErr: true, 2032 }, 2033 { 2034 name: "pass if selector targets an existing MachineDeploymentClass InfrastructureTemplate with prefix *", 2035 selector: clusterv1.PatchSelector{ 2036 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2037 Kind: "InfrastructureMachineTemplate", 2038 MatchResources: clusterv1.PatchSelectorMatch{ 2039 MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{ 2040 Names: []string{"a-*"}, 2041 }, 2042 }, 2043 }, 2044 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 2045 WithWorkerMachineDeploymentClasses( 2046 *builder.MachineDeploymentClass("a-something-a"). 2047 WithInfrastructureTemplate( 2048 refToUnstructured(&corev1.ObjectReference{ 2049 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2050 Kind: "InfrastructureMachineTemplate", 2051 })). 2052 WithBootstrapTemplate( 2053 refToUnstructured(&corev1.ObjectReference{ 2054 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 2055 Kind: "BootstrapTemplate", 2056 })). 2057 Build(), 2058 ). 2059 Build(), 2060 }, 2061 { 2062 name: "pass if selector targets an existing MachinePoolClass InfrastructureTemplate with prefix *", 2063 selector: clusterv1.PatchSelector{ 2064 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2065 Kind: "InfrastructureMachinePoolTemplate", 2066 MatchResources: clusterv1.PatchSelectorMatch{ 2067 MachinePoolClass: &clusterv1.PatchSelectorMatchMachinePoolClass{ 2068 Names: []string{"a-*"}, 2069 }, 2070 }, 2071 }, 2072 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 2073 WithWorkerMachinePoolClasses( 2074 *builder.MachinePoolClass("a-something-a"). 2075 WithInfrastructureTemplate( 2076 refToUnstructured(&corev1.ObjectReference{ 2077 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2078 Kind: "InfrastructureMachinePoolTemplate", 2079 })). 2080 WithBootstrapTemplate( 2081 refToUnstructured(&corev1.ObjectReference{ 2082 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 2083 Kind: "BootstrapTemplate", 2084 })). 2085 Build(), 2086 ). 2087 Build(), 2088 }, 2089 { 2090 name: "pass if selector targets an existing MachineDeploymentClass InfrastructureTemplate with suffix *", 2091 selector: clusterv1.PatchSelector{ 2092 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2093 Kind: "InfrastructureMachineTemplate", 2094 MatchResources: clusterv1.PatchSelectorMatch{ 2095 MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{ 2096 Names: []string{"*-a"}, 2097 }, 2098 }, 2099 }, 2100 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 2101 WithWorkerMachineDeploymentClasses( 2102 *builder.MachineDeploymentClass("a-something-a"). 2103 WithInfrastructureTemplate( 2104 refToUnstructured(&corev1.ObjectReference{ 2105 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2106 Kind: "InfrastructureMachineTemplate", 2107 })). 2108 WithBootstrapTemplate( 2109 refToUnstructured(&corev1.ObjectReference{ 2110 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 2111 Kind: "BootstrapTemplate", 2112 })). 2113 Build(), 2114 ). 2115 Build(), 2116 }, 2117 { 2118 name: "pass if selector targets an existing MachinePoolClass InfrastructureTemplate with suffix *", 2119 selector: clusterv1.PatchSelector{ 2120 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2121 Kind: "InfrastructureMachinePoolTemplate", 2122 MatchResources: clusterv1.PatchSelectorMatch{ 2123 MachinePoolClass: &clusterv1.PatchSelectorMatchMachinePoolClass{ 2124 Names: []string{"*-a"}, 2125 }, 2126 }, 2127 }, 2128 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 2129 WithWorkerMachinePoolClasses( 2130 *builder.MachinePoolClass("a-something-a"). 2131 WithInfrastructureTemplate( 2132 refToUnstructured(&corev1.ObjectReference{ 2133 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2134 Kind: "InfrastructureMachinePoolTemplate", 2135 })). 2136 WithBootstrapTemplate( 2137 refToUnstructured(&corev1.ObjectReference{ 2138 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 2139 Kind: "BootstrapTemplate", 2140 })). 2141 Build(), 2142 ). 2143 Build(), 2144 }, 2145 { 2146 name: "pass if selector targets all existing MachineDeploymentClass InfrastructureTemplate with *", 2147 selector: clusterv1.PatchSelector{ 2148 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2149 Kind: "InfrastructureMachineTemplate", 2150 MatchResources: clusterv1.PatchSelectorMatch{ 2151 MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{ 2152 Names: []string{"*"}, 2153 }, 2154 }, 2155 }, 2156 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 2157 WithWorkerMachineDeploymentClasses( 2158 *builder.MachineDeploymentClass("a-something-a"). 2159 WithInfrastructureTemplate( 2160 refToUnstructured(&corev1.ObjectReference{ 2161 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2162 Kind: "InfrastructureMachineTemplate", 2163 })). 2164 WithBootstrapTemplate( 2165 refToUnstructured(&corev1.ObjectReference{ 2166 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 2167 Kind: "BootstrapTemplate", 2168 })). 2169 Build(), 2170 ). 2171 Build(), 2172 }, 2173 { 2174 name: "pass if selector targets all existing MachinePoolClass InfrastructureTemplate with *", 2175 selector: clusterv1.PatchSelector{ 2176 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2177 Kind: "InfrastructureMachinePoolTemplate", 2178 MatchResources: clusterv1.PatchSelectorMatch{ 2179 MachinePoolClass: &clusterv1.PatchSelectorMatchMachinePoolClass{ 2180 Names: []string{"*"}, 2181 }, 2182 }, 2183 }, 2184 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 2185 WithWorkerMachinePoolClasses( 2186 *builder.MachinePoolClass("a-something-a"). 2187 WithInfrastructureTemplate( 2188 refToUnstructured(&corev1.ObjectReference{ 2189 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2190 Kind: "InfrastructureMachinePoolTemplate", 2191 })). 2192 WithBootstrapTemplate( 2193 refToUnstructured(&corev1.ObjectReference{ 2194 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 2195 Kind: "BootstrapTemplate", 2196 })). 2197 Build(), 2198 ). 2199 Build(), 2200 }, 2201 // The following tests have selectors which match multiple resources at the same time. 2202 { 2203 name: "fail if selector targets a matching infrastructureCluster reference and a not matching control plane", 2204 selector: clusterv1.PatchSelector{ 2205 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2206 Kind: "InfrastructureClusterTemplate", 2207 MatchResources: clusterv1.PatchSelectorMatch{ 2208 InfrastructureCluster: true, 2209 ControlPlane: true, 2210 }, 2211 }, 2212 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 2213 WithInfrastructureClusterTemplate( 2214 refToUnstructured( 2215 &corev1.ObjectReference{ 2216 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2217 Kind: "InfrastructureClusterTemplate", 2218 }), 2219 ). 2220 WithControlPlaneTemplate( 2221 refToUnstructured( 2222 &corev1.ObjectReference{ 2223 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 2224 Kind: "NonMatchingControlPlaneTemplate", 2225 }), 2226 ). 2227 WithControlPlaneInfrastructureMachineTemplate( 2228 refToUnstructured( 2229 &corev1.ObjectReference{ 2230 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2231 Kind: "NonMatchingInfrastructureMachineTemplate", 2232 }), 2233 ). 2234 Build(), 2235 wantErr: true, 2236 }, 2237 { 2238 name: "pass if selector targets BOTH an existing ControlPlane MachineInfrastructureTemplate and an existing MachineDeploymentClass InfrastructureTemplate", 2239 selector: clusterv1.PatchSelector{ 2240 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2241 Kind: "InfrastructureMachineTemplate", 2242 MatchResources: clusterv1.PatchSelectorMatch{ 2243 MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{ 2244 Names: []string{"bb"}, 2245 }, 2246 ControlPlane: true, 2247 }, 2248 }, 2249 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 2250 WithControlPlaneInfrastructureMachineTemplate( 2251 refToUnstructured( 2252 &corev1.ObjectReference{ 2253 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2254 Kind: "InfrastructureMachineTemplate", 2255 }), 2256 ). 2257 WithWorkerMachineDeploymentClasses( 2258 *builder.MachineDeploymentClass("aa"). 2259 WithInfrastructureTemplate( 2260 refToUnstructured(&corev1.ObjectReference{ 2261 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2262 Kind: "InfrastructureMachineTemplate", 2263 })). 2264 WithBootstrapTemplate( 2265 refToUnstructured(&corev1.ObjectReference{ 2266 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 2267 Kind: "BootstrapTemplate", 2268 })). 2269 Build(), 2270 *builder.MachineDeploymentClass("bb"). 2271 WithInfrastructureTemplate( 2272 refToUnstructured(&corev1.ObjectReference{ 2273 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2274 Kind: "InfrastructureMachineTemplate", 2275 })). 2276 WithBootstrapTemplate( 2277 refToUnstructured(&corev1.ObjectReference{ 2278 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 2279 Kind: "BootstrapTemplate", 2280 })). 2281 Build(), 2282 ). 2283 Build(), 2284 wantErr: false, 2285 }, 2286 { 2287 name: "fail if selector targets BOTH an existing ControlPlane MachineInfrastructureTemplate and an existing MachineDeploymentClass InfrastructureTemplate but does not match all MachineDeployment classes", 2288 selector: clusterv1.PatchSelector{ 2289 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2290 Kind: "InfrastructureMachineTemplate", 2291 MatchResources: clusterv1.PatchSelectorMatch{ 2292 MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{ 2293 Names: []string{"aa", "bb"}, 2294 }, 2295 ControlPlane: true, 2296 }, 2297 }, 2298 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 2299 WithControlPlaneTemplate( 2300 refToUnstructured( 2301 &corev1.ObjectReference{ 2302 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 2303 Kind: "NonMatchingControlPlaneTemplate", 2304 }), 2305 ). 2306 WithControlPlaneInfrastructureMachineTemplate( 2307 refToUnstructured( 2308 &corev1.ObjectReference{ 2309 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2310 Kind: "InfrastructureMachineTemplate", 2311 }), 2312 ). 2313 WithWorkerMachineDeploymentClasses( 2314 *builder.MachineDeploymentClass("aa"). 2315 WithInfrastructureTemplate( 2316 refToUnstructured(&corev1.ObjectReference{ 2317 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2318 Kind: "NonMatchingInfrastructureMachineTemplate", 2319 })). 2320 WithBootstrapTemplate( 2321 refToUnstructured(&corev1.ObjectReference{ 2322 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 2323 Kind: "BootstrapTemplate", 2324 })). 2325 Build(), 2326 *builder.MachineDeploymentClass("bb"). 2327 WithInfrastructureTemplate( 2328 refToUnstructured(&corev1.ObjectReference{ 2329 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2330 Kind: "InfrastructureMachineTemplate", 2331 })). 2332 WithBootstrapTemplate( 2333 refToUnstructured(&corev1.ObjectReference{ 2334 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 2335 Kind: "BootstrapTemplate", 2336 })). 2337 Build(), 2338 ). 2339 Build(), 2340 wantErr: true, 2341 }, 2342 { 2343 name: "fail if selector targets BOTH an existing ControlPlane MachineInfrastructureTemplate and an existing MachineDeploymentClass InfrastructureTemplate but matches only one", 2344 selector: clusterv1.PatchSelector{ 2345 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2346 Kind: "InfrastructureMachineTemplate", 2347 MatchResources: clusterv1.PatchSelectorMatch{ 2348 MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{ 2349 Names: []string{"bb"}, 2350 }, 2351 ControlPlane: true, 2352 }, 2353 }, 2354 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 2355 WithControlPlaneTemplate( 2356 refToUnstructured( 2357 &corev1.ObjectReference{ 2358 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 2359 Kind: "NonMatchingControlPlaneTemplate", 2360 }), 2361 ). 2362 WithControlPlaneInfrastructureMachineTemplate( 2363 refToUnstructured( 2364 &corev1.ObjectReference{ 2365 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2366 Kind: "InfrastructureMachineTemplate", 2367 }), 2368 ). 2369 WithWorkerMachineDeploymentClasses( 2370 *builder.MachineDeploymentClass("bb"). 2371 WithInfrastructureTemplate( 2372 refToUnstructured(&corev1.ObjectReference{ 2373 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2374 Kind: "OtherInfrastructureMachineTemplate", 2375 })). 2376 WithBootstrapTemplate( 2377 refToUnstructured(&corev1.ObjectReference{ 2378 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 2379 Kind: "BootstrapTemplate", 2380 })). 2381 Build(), 2382 ). 2383 Build(), 2384 wantErr: true, 2385 }, 2386 { 2387 name: "fail if selector targets everything but nothing matches", 2388 selector: clusterv1.PatchSelector{ 2389 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2390 Kind: "NotMatchingInfrastructureMachineTemplate", 2391 MatchResources: clusterv1.PatchSelectorMatch{ 2392 ControlPlane: true, 2393 InfrastructureCluster: true, 2394 MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{ 2395 Names: []string{"bb"}, 2396 }, 2397 }, 2398 }, 2399 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 2400 WithInfrastructureClusterTemplate( 2401 refToUnstructured( 2402 &corev1.ObjectReference{ 2403 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2404 Kind: "InfrastructureClusterTemplate", 2405 }), 2406 ). 2407 WithControlPlaneTemplate( 2408 refToUnstructured( 2409 &corev1.ObjectReference{ 2410 APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", 2411 Kind: "NonMatchingControlPlaneTemplate", 2412 }), 2413 ). 2414 WithControlPlaneInfrastructureMachineTemplate( 2415 refToUnstructured( 2416 &corev1.ObjectReference{ 2417 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2418 Kind: "InfrastructureMachineTemplate", 2419 }), 2420 ). 2421 WithWorkerMachineDeploymentClasses( 2422 *builder.MachineDeploymentClass("bb"). 2423 WithInfrastructureTemplate( 2424 refToUnstructured(&corev1.ObjectReference{ 2425 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 2426 Kind: "OtherInfrastructureMachineTemplate", 2427 })). 2428 WithBootstrapTemplate( 2429 refToUnstructured(&corev1.ObjectReference{ 2430 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 2431 Kind: "BootstrapTemplate", 2432 })). 2433 Build(), 2434 ). 2435 Build(), 2436 wantErr: true, 2437 }, 2438 } 2439 for _, tt := range tests { 2440 t.Run(tt.name, func(t *testing.T) { 2441 g := NewWithT(t) 2442 2443 err := validateSelectors(tt.selector, tt.clusterClass, field.NewPath("")) 2444 2445 if tt.wantErr { 2446 g.Expect(err.ToAggregate()).To(HaveOccurred()) 2447 return 2448 } 2449 g.Expect(err.ToAggregate()).ToNot(HaveOccurred()) 2450 }) 2451 } 2452 } 2453 2454 func TestGetVariableName(t *testing.T) { 2455 tests := []struct { 2456 name string 2457 variable string 2458 variableName string 2459 }{ 2460 { 2461 name: "simple variable", 2462 variable: "variableA", 2463 variableName: "variableA", 2464 }, 2465 { 2466 name: "variable object", 2467 variable: "variableObject.field", 2468 variableName: "variableObject", 2469 }, 2470 { 2471 name: "variable array", 2472 variable: "variableArray[0]", 2473 variableName: "variableArray", 2474 }, 2475 } 2476 2477 for _, tt := range tests { 2478 t.Run(tt.name, func(t *testing.T) { 2479 g := NewWithT(t) 2480 2481 g.Expect(getVariableName(tt.variable)).To(Equal(tt.variableName)) 2482 }) 2483 } 2484 }