sigs.k8s.io/cluster-api@v1.7.1/internal/topology/variables/clusterclass_variable_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 variables 18 19 import ( 20 "context" 21 "testing" 22 23 . "github.com/onsi/gomega" 24 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 25 "k8s.io/apimachinery/pkg/util/validation/field" 26 "k8s.io/utils/ptr" 27 28 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 29 ) 30 31 func Test_ValidateClusterClassVariables(t *testing.T) { 32 tests := []struct { 33 name string 34 clusterClassVariables []clusterv1.ClusterClassVariable 35 wantErr bool 36 }{ 37 { 38 name: "Error if multiple variables share a name", 39 clusterClassVariables: []clusterv1.ClusterClassVariable{ 40 { 41 Name: "cpu", 42 Schema: clusterv1.VariableSchema{ 43 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 44 Type: "integer", 45 Minimum: ptr.To[int64](1), 46 }, 47 }, 48 }, 49 { 50 Name: "cpu", 51 Schema: clusterv1.VariableSchema{ 52 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 53 Type: "integer", 54 Minimum: ptr.To[int64](1), 55 }, 56 }, 57 }, 58 }, 59 wantErr: true, 60 }, 61 { 62 name: "Pass multiple variable validation", 63 clusterClassVariables: []clusterv1.ClusterClassVariable{ 64 { 65 Name: "cpu", 66 Schema: clusterv1.VariableSchema{ 67 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 68 Type: "integer", 69 Minimum: ptr.To[int64](1), 70 }, 71 }, 72 }, 73 { 74 Name: "validNumber", 75 Schema: clusterv1.VariableSchema{ 76 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 77 Type: "number", 78 Maximum: ptr.To[int64](1), 79 }, 80 }, 81 }, 82 83 { 84 Name: "validVariable", 85 Schema: clusterv1.VariableSchema{ 86 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 87 Type: "string", 88 MinLength: ptr.To[int64](1), 89 }, 90 }, 91 }, 92 { 93 Name: "location", 94 Schema: clusterv1.VariableSchema{ 95 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 96 Type: "string", 97 MinLength: ptr.To[int64](1), 98 }, 99 }, 100 }, 101 }, 102 }, 103 } 104 105 for _, tt := range tests { 106 t.Run(tt.name, func(t *testing.T) { 107 g := NewWithT(t) 108 109 errList := ValidateClusterClassVariables(context.TODO(), 110 tt.clusterClassVariables, 111 field.NewPath("spec", "variables")) 112 113 if tt.wantErr { 114 g.Expect(errList).NotTo(BeEmpty()) 115 return 116 } 117 g.Expect(errList).To(BeEmpty()) 118 }) 119 } 120 } 121 122 func Test_ValidateClusterClassVariable(t *testing.T) { 123 tests := []struct { 124 name string 125 clusterClassVariable *clusterv1.ClusterClassVariable 126 wantErr bool 127 }{ 128 { 129 name: "Valid integer schema", 130 clusterClassVariable: &clusterv1.ClusterClassVariable{ 131 Name: "cpu", 132 Schema: clusterv1.VariableSchema{ 133 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 134 Type: "integer", 135 Minimum: ptr.To[int64](1), 136 }, 137 }, 138 }, 139 }, 140 { 141 name: "Valid string schema", 142 clusterClassVariable: &clusterv1.ClusterClassVariable{ 143 Name: "location", 144 Schema: clusterv1.VariableSchema{ 145 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 146 Type: "string", 147 MinLength: ptr.To[int64](1), 148 }, 149 }, 150 }, 151 }, 152 { 153 name: "Valid variable name", 154 clusterClassVariable: &clusterv1.ClusterClassVariable{ 155 Name: "validVariable", 156 Schema: clusterv1.VariableSchema{ 157 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 158 Type: "string", 159 MinLength: ptr.To[int64](1), 160 }, 161 }, 162 }, 163 }, 164 { 165 name: "fail on variable name is builtin", 166 clusterClassVariable: &clusterv1.ClusterClassVariable{ 167 Name: "builtin", 168 Schema: clusterv1.VariableSchema{ 169 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 170 Type: "string", 171 MinLength: ptr.To[int64](1), 172 }, 173 }, 174 }, 175 wantErr: true, 176 }, 177 { 178 name: "fail on empty variable name", 179 clusterClassVariable: &clusterv1.ClusterClassVariable{ 180 Name: "", 181 Schema: clusterv1.VariableSchema{ 182 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 183 Type: "string", 184 MinLength: ptr.To[int64](1), 185 }, 186 }, 187 }, 188 wantErr: true, 189 }, 190 { 191 name: "fail on variable name containing dot (.)", 192 clusterClassVariable: &clusterv1.ClusterClassVariable{ 193 Name: "path.tovariable", 194 Schema: clusterv1.VariableSchema{ 195 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 196 Type: "string", 197 MinLength: ptr.To[int64](1), 198 }, 199 }, 200 }, 201 wantErr: true, 202 }, 203 { 204 name: "Valid variable metadata", 205 clusterClassVariable: &clusterv1.ClusterClassVariable{ 206 Name: "validVariable", 207 Metadata: clusterv1.ClusterClassVariableMetadata{ 208 Labels: map[string]string{ 209 "label-key": "label-value", 210 }, 211 Annotations: map[string]string{ 212 "annotation-key": "annotation-value", 213 }, 214 }, 215 Schema: clusterv1.VariableSchema{ 216 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 217 Type: "string", 218 MinLength: ptr.To[int64](1), 219 }, 220 }, 221 }, 222 }, 223 { 224 name: "fail on invalid variable label: key does not start with alphanumeric character", 225 clusterClassVariable: &clusterv1.ClusterClassVariable{ 226 Name: "path.tovariable", 227 Metadata: clusterv1.ClusterClassVariableMetadata{ 228 Labels: map[string]string{ 229 ".label-key": "label-value", 230 }, 231 }, 232 Schema: clusterv1.VariableSchema{ 233 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 234 Type: "string", 235 MinLength: ptr.To[int64](1), 236 }, 237 }, 238 }, 239 wantErr: true, 240 }, 241 { 242 name: "fail on invalid variable annotation: key does not start with alphanumeric character", 243 clusterClassVariable: &clusterv1.ClusterClassVariable{ 244 Name: "path.tovariable", 245 Metadata: clusterv1.ClusterClassVariableMetadata{ 246 Annotations: map[string]string{ 247 ".annotation-key": "annotation-value", 248 }, 249 }, 250 Schema: clusterv1.VariableSchema{ 251 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 252 Type: "string", 253 MinLength: ptr.To[int64](1), 254 }, 255 }, 256 }, 257 wantErr: true, 258 }, 259 { 260 name: "Valid default value regular string", 261 clusterClassVariable: &clusterv1.ClusterClassVariable{ 262 Name: "var", 263 Required: true, 264 Schema: clusterv1.VariableSchema{ 265 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 266 Type: "string", 267 Default: &apiextensionsv1.JSON{ 268 Raw: []byte(`"defaultValue"`), 269 }, 270 }, 271 }, 272 }, 273 }, 274 { 275 name: "fail on default value with invalid JSON", 276 clusterClassVariable: &clusterv1.ClusterClassVariable{ 277 Name: "var", 278 Required: true, 279 Schema: clusterv1.VariableSchema{ 280 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 281 Type: "string", 282 Default: &apiextensionsv1.JSON{ 283 Raw: []byte(`"defaultValue": "value"`), // invalid JSON 284 }, 285 }, 286 }, 287 }, 288 wantErr: true, 289 }, 290 { 291 name: "Valid example value regular string", 292 clusterClassVariable: &clusterv1.ClusterClassVariable{ 293 Name: "var", 294 Required: true, 295 Schema: clusterv1.VariableSchema{ 296 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 297 Type: "string", 298 Example: &apiextensionsv1.JSON{ 299 Raw: []byte(`"exampleValue"`), 300 }, 301 }, 302 }, 303 }, 304 }, 305 { 306 name: "fail on example value with invalid JSON", 307 clusterClassVariable: &clusterv1.ClusterClassVariable{ 308 Name: "var", 309 Required: true, 310 Schema: clusterv1.VariableSchema{ 311 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 312 Type: "string", 313 Example: &apiextensionsv1.JSON{ 314 Raw: []byte(`"exampleValue": "value"`), // invalid JSON 315 }, 316 }, 317 }, 318 }, 319 wantErr: true, 320 }, 321 { 322 name: "Valid enum values", 323 clusterClassVariable: &clusterv1.ClusterClassVariable{ 324 Name: "var", 325 Required: true, 326 Schema: clusterv1.VariableSchema{ 327 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 328 Type: "string", 329 Enum: []apiextensionsv1.JSON{ 330 {Raw: []byte(`"enumValue1"`)}, 331 {Raw: []byte(`"enumValue2"`)}, 332 }, 333 }, 334 }, 335 }, 336 }, 337 { 338 name: "fail on enum value with invalid JSON", 339 clusterClassVariable: &clusterv1.ClusterClassVariable{ 340 Name: "var", 341 Required: true, 342 Schema: clusterv1.VariableSchema{ 343 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 344 Type: "string", 345 Enum: []apiextensionsv1.JSON{ 346 {Raw: []byte(`"defaultValue": "value"`)}, // invalid JSON 347 }, 348 }, 349 }, 350 }, 351 wantErr: true, 352 }, 353 { 354 name: "fail on variable type is null", 355 clusterClassVariable: &clusterv1.ClusterClassVariable{ 356 Name: "var", 357 Schema: clusterv1.VariableSchema{ 358 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 359 Type: "null", 360 }, 361 }, 362 }, 363 wantErr: true, 364 }, 365 { 366 name: "fail on variable type is not valid", 367 clusterClassVariable: &clusterv1.ClusterClassVariable{ 368 Name: "var", 369 Schema: clusterv1.VariableSchema{ 370 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 371 Type: "invalidVariableType", 372 }, 373 }, 374 }, 375 wantErr: true, 376 }, 377 { 378 name: "fail on variable type length zero", 379 clusterClassVariable: &clusterv1.ClusterClassVariable{ 380 Name: "var", 381 Schema: clusterv1.VariableSchema{ 382 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 383 Type: "", 384 }, 385 }, 386 }, 387 wantErr: true, 388 }, 389 { 390 name: "Valid object schema", 391 clusterClassVariable: &clusterv1.ClusterClassVariable{ 392 Name: "httpProxy", 393 Required: true, 394 Schema: clusterv1.VariableSchema{ 395 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 396 Type: "object", 397 Properties: map[string]clusterv1.JSONSchemaProps{ 398 "enabled": { 399 Type: "boolean", 400 Default: &apiextensionsv1.JSON{Raw: []byte(`false`)}, 401 }, 402 "url": { 403 Type: "string", 404 }, 405 "noProxy": { 406 Type: "string", 407 }, 408 }, 409 }, 410 }, 411 }, 412 }, 413 { 414 name: "fail on invalid object schema", 415 clusterClassVariable: &clusterv1.ClusterClassVariable{ 416 Name: "httpProxy", 417 Required: true, 418 Schema: clusterv1.VariableSchema{ 419 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 420 Type: "object", 421 Properties: map[string]clusterv1.JSONSchemaProps{ 422 "enabled": { 423 Type: "boolean", 424 Default: &apiextensionsv1.JSON{Raw: []byte(`false`)}, 425 }, 426 "url": { 427 Type: "string", 428 }, 429 "noProxy": { 430 Type: "invalidType", // invalid type. 431 }, 432 }, 433 }, 434 }, 435 }, 436 wantErr: true, 437 }, 438 { 439 name: "Valid map schema", 440 clusterClassVariable: &clusterv1.ClusterClassVariable{ 441 Name: "httpProxy", 442 Required: true, 443 Schema: clusterv1.VariableSchema{ 444 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 445 Type: "object", 446 AdditionalProperties: &clusterv1.JSONSchemaProps{ 447 Type: "object", 448 Properties: map[string]clusterv1.JSONSchemaProps{ 449 "enabled": { 450 Type: "boolean", 451 Default: &apiextensionsv1.JSON{Raw: []byte(`false`)}, 452 }, 453 "url": { 454 Type: "string", 455 }, 456 "noProxy": { 457 Type: "string", 458 }, 459 }, 460 }, 461 }, 462 }, 463 }, 464 }, 465 { 466 name: "fail on invalid map schema", 467 clusterClassVariable: &clusterv1.ClusterClassVariable{ 468 Name: "httpProxy", 469 Required: true, 470 Schema: clusterv1.VariableSchema{ 471 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 472 Type: "object", 473 AdditionalProperties: &clusterv1.JSONSchemaProps{ 474 Type: "object", 475 Properties: map[string]clusterv1.JSONSchemaProps{ 476 "enabled": { 477 Type: "boolean", 478 Default: &apiextensionsv1.JSON{Raw: []byte(`false`)}, 479 }, 480 "url": { 481 Type: "string", 482 }, 483 "noProxy": { 484 Type: "invalidType", // invalid type. 485 }, 486 }, 487 }, 488 }, 489 }, 490 }, 491 wantErr: true, 492 }, 493 { 494 name: "fail on object (properties) and map (additionalProperties) both set", 495 clusterClassVariable: &clusterv1.ClusterClassVariable{ 496 Name: "httpProxy", 497 Required: true, 498 Schema: clusterv1.VariableSchema{ 499 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 500 Type: "object", 501 AdditionalProperties: &clusterv1.JSONSchemaProps{ 502 Type: "object", 503 Properties: map[string]clusterv1.JSONSchemaProps{ 504 "enabled": { 505 Type: "boolean", 506 Default: &apiextensionsv1.JSON{Raw: []byte(`false`)}, 507 }, 508 }, 509 }, 510 Properties: map[string]clusterv1.JSONSchemaProps{ 511 "enabled": { 512 Type: "boolean", 513 Default: &apiextensionsv1.JSON{Raw: []byte(`false`)}, 514 }, 515 }, 516 }, 517 }, 518 }, 519 wantErr: true, 520 }, 521 { 522 name: "Valid array schema", 523 clusterClassVariable: &clusterv1.ClusterClassVariable{ 524 Name: "arrayVariable", 525 Required: true, 526 Schema: clusterv1.VariableSchema{ 527 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 528 Type: "array", 529 Items: &clusterv1.JSONSchemaProps{ 530 Type: "boolean", 531 Default: &apiextensionsv1.JSON{Raw: []byte(`false`)}, 532 }, 533 }, 534 }, 535 }, 536 }, 537 { 538 name: "fail on invalid array schema", 539 clusterClassVariable: &clusterv1.ClusterClassVariable{ 540 Name: "arrayVariable", 541 Schema: clusterv1.VariableSchema{ 542 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 543 Type: "array", 544 Items: &clusterv1.JSONSchemaProps{ 545 Type: "string", 546 Default: &apiextensionsv1.JSON{Raw: []byte(`invalidString`)}, // missing quotes. 547 }, 548 }, 549 }, 550 }, 551 wantErr: true, 552 }, 553 { 554 name: "pass on variable with required set true with a default defined", 555 clusterClassVariable: &clusterv1.ClusterClassVariable{ 556 Name: "var", 557 Required: true, 558 Schema: clusterv1.VariableSchema{ 559 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 560 Type: "string", 561 Default: &apiextensionsv1.JSON{Raw: []byte(`"defaultValue"`)}, 562 }, 563 }, 564 }, 565 }, 566 { 567 name: "pass on variable with a default that is valid by the given schema", 568 clusterClassVariable: &clusterv1.ClusterClassVariable{ 569 Name: "var", 570 Schema: clusterv1.VariableSchema{ 571 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 572 Type: "string", 573 MaxLength: ptr.To[int64](6), 574 Default: &apiextensionsv1.JSON{Raw: []byte(`"short"`)}, 575 }, 576 }, 577 }, 578 }, 579 { 580 name: "fail on variable with a default that is invalid by the given schema", 581 clusterClassVariable: &clusterv1.ClusterClassVariable{ 582 Name: "var", 583 Schema: clusterv1.VariableSchema{ 584 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 585 Type: "string", 586 MaxLength: ptr.To[int64](6), 587 Default: &apiextensionsv1.JSON{Raw: []byte(`"veryLongValueIsInvalid"`)}, 588 }, 589 }, 590 }, 591 wantErr: true, 592 }, 593 { 594 name: "pass if variable is an object with default value valid by the given schema", 595 clusterClassVariable: &clusterv1.ClusterClassVariable{ 596 Name: "var", 597 Schema: clusterv1.VariableSchema{ 598 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 599 Type: "object", 600 Properties: map[string]clusterv1.JSONSchemaProps{ 601 "spec": { 602 Type: "object", 603 Properties: map[string]clusterv1.JSONSchemaProps{ 604 "replicas": { 605 Type: "integer", 606 Default: &apiextensionsv1.JSON{Raw: []byte(`100`)}, 607 Minimum: ptr.To[int64](1), 608 }, 609 }, 610 }, 611 }, 612 }, 613 }, 614 }, 615 }, 616 { 617 name: "fail if variable is an object with default value invalidated by the given schema", 618 clusterClassVariable: &clusterv1.ClusterClassVariable{ 619 Name: "var", 620 Schema: clusterv1.VariableSchema{ 621 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 622 Type: "object", 623 Properties: map[string]clusterv1.JSONSchemaProps{ 624 "spec": { 625 Type: "object", 626 Properties: map[string]clusterv1.JSONSchemaProps{ 627 "replicas": { 628 Type: "integer", 629 Default: &apiextensionsv1.JSON{Raw: []byte(`-100`)}, 630 Minimum: ptr.To[int64](1), 631 }, 632 }, 633 }, 634 }, 635 }, 636 }, 637 }, 638 wantErr: true, 639 }, 640 { 641 name: "fail if variable is an object with a top level default value invalidated by the given schema", 642 clusterClassVariable: &clusterv1.ClusterClassVariable{ 643 Name: "var", 644 Schema: clusterv1.VariableSchema{ 645 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 646 Type: "object", 647 Default: &apiextensionsv1.JSON{ 648 Raw: []byte(`{"spec":{"replicas": -100}}`), 649 }, 650 Properties: map[string]clusterv1.JSONSchemaProps{ 651 "spec": { 652 Type: "object", 653 Properties: map[string]clusterv1.JSONSchemaProps{ 654 "replicas": { 655 Type: "integer", 656 Minimum: ptr.To[int64](1), 657 }, 658 }, 659 }, 660 }, 661 }, 662 }, 663 }, 664 wantErr: true, 665 }, 666 { 667 name: "pass if variable is an object with a top level default value valid by the given schema", 668 clusterClassVariable: &clusterv1.ClusterClassVariable{ 669 Name: "var", 670 Schema: clusterv1.VariableSchema{ 671 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 672 Type: "object", 673 Default: &apiextensionsv1.JSON{ 674 Raw: []byte(`{"spec":{"replicas": 100}}`), 675 }, 676 Properties: map[string]clusterv1.JSONSchemaProps{ 677 "spec": { 678 Type: "object", 679 Properties: map[string]clusterv1.JSONSchemaProps{ 680 "replicas": { 681 Type: "integer", 682 Minimum: ptr.To[int64](1), 683 }, 684 }, 685 }, 686 }, 687 }, 688 }, 689 }, 690 }, 691 { 692 name: "fail if field is required below properties and sets a default that misses the field", 693 clusterClassVariable: &clusterv1.ClusterClassVariable{ 694 Name: "var", 695 Schema: clusterv1.VariableSchema{ 696 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 697 Type: "object", 698 Properties: map[string]clusterv1.JSONSchemaProps{ 699 "spec": { 700 Type: "object", 701 Required: []string{"replicas"}, 702 Default: &apiextensionsv1.JSON{ 703 // replicas missing results in failure 704 Raw: []byte(`{"value": 100}`), 705 }, 706 Properties: map[string]clusterv1.JSONSchemaProps{ 707 "replicas": { 708 Type: "integer", 709 Default: &apiextensionsv1.JSON{Raw: []byte(`100`)}, 710 Minimum: ptr.To[int64](1), 711 }, 712 "value": { 713 Type: "integer", 714 Default: &apiextensionsv1.JSON{Raw: []byte(`100`)}, 715 Minimum: ptr.To[int64](1), 716 }, 717 }, 718 }, 719 }, 720 }, 721 }, 722 }, 723 wantErr: true, 724 }, 725 { 726 name: "pass if field is required below properties and sets a default", 727 clusterClassVariable: &clusterv1.ClusterClassVariable{ 728 Name: "var", 729 Schema: clusterv1.VariableSchema{ 730 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 731 Type: "object", 732 Properties: map[string]clusterv1.JSONSchemaProps{ 733 "spec": { 734 Type: "object", 735 Required: []string{"replicas"}, 736 Default: &apiextensionsv1.JSON{ 737 // replicas is set here so the `required` property is met. 738 Raw: []byte(`{"replicas": 100}`), 739 }, 740 Properties: map[string]clusterv1.JSONSchemaProps{ 741 "replicas": { 742 Type: "integer", 743 Default: &apiextensionsv1.JSON{Raw: []byte(`100`)}, 744 Minimum: ptr.To[int64](1), 745 }, 746 }, 747 }, 748 }, 749 }, 750 }, 751 }, 752 }, 753 754 { 755 name: "pass on variable with an example that is valid by the given schema", 756 clusterClassVariable: &clusterv1.ClusterClassVariable{ 757 Name: "var", 758 Schema: clusterv1.VariableSchema{ 759 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 760 Type: "string", 761 MaxLength: ptr.To[int64](6), 762 Example: &apiextensionsv1.JSON{Raw: []byte(`"short"`)}, 763 }, 764 }, 765 }, 766 }, 767 { 768 name: "fail on variable with an example that is invalid by the given schema", 769 clusterClassVariable: &clusterv1.ClusterClassVariable{ 770 Name: "var", 771 Schema: clusterv1.VariableSchema{ 772 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 773 Type: "string", 774 MaxLength: ptr.To[int64](6), 775 Example: &apiextensionsv1.JSON{Raw: []byte(`"veryLongValueIsInvalid"`)}, 776 }, 777 }, 778 }, 779 wantErr: true, 780 }, 781 { 782 name: "pass if variable is an object with an example valid by the given schema", 783 clusterClassVariable: &clusterv1.ClusterClassVariable{ 784 Name: "var", 785 Schema: clusterv1.VariableSchema{ 786 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 787 Type: "object", 788 Properties: map[string]clusterv1.JSONSchemaProps{ 789 "spec": { 790 Type: "object", 791 Properties: map[string]clusterv1.JSONSchemaProps{ 792 "replicas": { 793 Type: "integer", 794 Minimum: ptr.To[int64](0), 795 Example: &apiextensionsv1.JSON{Raw: []byte(`100`)}, 796 }, 797 }, 798 }, 799 }, 800 }, 801 }, 802 }, 803 }, 804 { 805 name: "fail if variable is an object with an example invalidated by the given schema", 806 clusterClassVariable: &clusterv1.ClusterClassVariable{ 807 Name: "var", 808 Schema: clusterv1.VariableSchema{ 809 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 810 Type: "object", 811 Properties: map[string]clusterv1.JSONSchemaProps{ 812 "spec": { 813 Type: "object", 814 Properties: map[string]clusterv1.JSONSchemaProps{ 815 "replicas": { 816 Type: "integer", 817 Minimum: ptr.To[int64](0), 818 Example: &apiextensionsv1.JSON{Raw: []byte(`-100`)}, 819 }, 820 }, 821 }, 822 }, 823 }, 824 }, 825 }, 826 wantErr: true, 827 }, 828 { 829 name: "fail if variable is an object with a top level example value invalidated by the given schema", 830 clusterClassVariable: &clusterv1.ClusterClassVariable{ 831 Name: "var", 832 Schema: clusterv1.VariableSchema{ 833 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 834 Type: "object", 835 Example: &apiextensionsv1.JSON{ 836 Raw: []byte(`{"spec":{"replicas": -100}}`), 837 }, 838 Properties: map[string]clusterv1.JSONSchemaProps{ 839 "spec": { 840 Type: "object", 841 Properties: map[string]clusterv1.JSONSchemaProps{ 842 "replicas": { 843 Type: "integer", 844 Minimum: ptr.To[int64](1), 845 }, 846 }, 847 }, 848 }, 849 }, 850 }, 851 }, 852 wantErr: true, 853 }, 854 { 855 name: "pass if variable is an object with a top level default value valid by the given schema", 856 clusterClassVariable: &clusterv1.ClusterClassVariable{ 857 Name: "var", 858 Schema: clusterv1.VariableSchema{ 859 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 860 Type: "object", 861 Example: &apiextensionsv1.JSON{ 862 Raw: []byte(`{"spec":{"replicas": 100}}`), 863 }, 864 Properties: map[string]clusterv1.JSONSchemaProps{ 865 "spec": { 866 Type: "object", 867 Properties: map[string]clusterv1.JSONSchemaProps{ 868 "replicas": { 869 Type: "integer", 870 Minimum: ptr.To[int64](1), 871 }, 872 }, 873 }, 874 }, 875 }, 876 }, 877 }, 878 }, 879 { 880 name: "pass on variable with an enum with all variables valid by the given schema", 881 clusterClassVariable: &clusterv1.ClusterClassVariable{ 882 Name: "var", 883 Schema: clusterv1.VariableSchema{ 884 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 885 Type: "string", 886 MaxLength: ptr.To[int64](6), 887 Enum: []apiextensionsv1.JSON{ 888 {Raw: []byte(`"short1"`)}, 889 {Raw: []byte(`"short2"`)}, 890 }, 891 }, 892 }, 893 }, 894 }, 895 { 896 name: "fail on variable with an enum with a value that is invalid by the given schema", 897 clusterClassVariable: &clusterv1.ClusterClassVariable{ 898 Name: "var", 899 Schema: clusterv1.VariableSchema{ 900 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 901 Type: "string", 902 MaxLength: ptr.To[int64](6), 903 Enum: []apiextensionsv1.JSON{ 904 {Raw: []byte(`"veryLongValueIsInvalid"`)}, 905 {Raw: []byte(`"short"`)}, 906 }, 907 }, 908 }, 909 }, 910 wantErr: true, 911 }, 912 { 913 name: "pass if variable is an object with an enum value that is valid by the given schema", 914 clusterClassVariable: &clusterv1.ClusterClassVariable{ 915 Name: "var", 916 Schema: clusterv1.VariableSchema{ 917 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 918 Type: "object", 919 Properties: map[string]clusterv1.JSONSchemaProps{ 920 "spec": { 921 Type: "object", 922 Properties: map[string]clusterv1.JSONSchemaProps{ 923 "replicas": { 924 Type: "integer", 925 Minimum: ptr.To[int64](0), 926 Enum: []apiextensionsv1.JSON{ 927 {Raw: []byte(`100`)}, 928 {Raw: []byte(`5`)}, 929 }, 930 }, 931 }, 932 }, 933 }, 934 }, 935 }, 936 }, 937 }, 938 { 939 name: "fail if variable is an object with an enum value invalidated by the given schema", 940 clusterClassVariable: &clusterv1.ClusterClassVariable{ 941 Name: "var", 942 Schema: clusterv1.VariableSchema{ 943 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 944 Type: "object", 945 Properties: map[string]clusterv1.JSONSchemaProps{ 946 "spec": { 947 Type: "object", 948 Properties: map[string]clusterv1.JSONSchemaProps{ 949 "replicas": { 950 Type: "integer", 951 Minimum: ptr.To[int64](0), 952 Enum: []apiextensionsv1.JSON{ 953 {Raw: []byte(`100`)}, 954 {Raw: []byte(`-100`)}, 955 }, 956 }, 957 }, 958 }, 959 }, 960 }, 961 }, 962 }, 963 wantErr: true, 964 }, 965 { 966 name: "fail if variable is an object with a top level enum value invalidated by the given schema", 967 clusterClassVariable: &clusterv1.ClusterClassVariable{ 968 Name: "var", 969 Schema: clusterv1.VariableSchema{ 970 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 971 Type: "object", 972 Enum: []apiextensionsv1.JSON{ 973 { 974 Raw: []byte(`{"spec":{"replicas": 100}}`), 975 }, 976 { 977 Raw: []byte(`{"spec":{"replicas": -100}}`), 978 }, 979 }, 980 Properties: map[string]clusterv1.JSONSchemaProps{ 981 "spec": { 982 Type: "object", 983 Properties: map[string]clusterv1.JSONSchemaProps{ 984 "replicas": { 985 Type: "integer", 986 Minimum: ptr.To[int64](1), 987 }, 988 }, 989 }, 990 }, 991 }, 992 }, 993 }, 994 wantErr: true, 995 }, 996 { 997 name: "pass if variable is an object with a top level enum value that is valid by the given schema", 998 clusterClassVariable: &clusterv1.ClusterClassVariable{ 999 Name: "var", 1000 Schema: clusterv1.VariableSchema{ 1001 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 1002 Type: "object", 1003 Enum: []apiextensionsv1.JSON{ 1004 { 1005 Raw: []byte(`{"spec":{"replicas": 100}}`), 1006 }, 1007 { 1008 Raw: []byte(`{"spec":{"replicas": 200}}`), 1009 }, 1010 }, 1011 Properties: map[string]clusterv1.JSONSchemaProps{ 1012 "spec": { 1013 Type: "object", 1014 Properties: map[string]clusterv1.JSONSchemaProps{ 1015 "replicas": { 1016 Type: "integer", 1017 Minimum: ptr.To[int64](1), 1018 }, 1019 }, 1020 }, 1021 }, 1022 }, 1023 }, 1024 }, 1025 }, 1026 } 1027 for _, tt := range tests { 1028 t.Run(tt.name, func(t *testing.T) { 1029 g := NewWithT(t) 1030 1031 errList := validateClusterClassVariable(context.TODO(), 1032 tt.clusterClassVariable, 1033 field.NewPath("spec", "variables").Index(0)) 1034 1035 if tt.wantErr { 1036 g.Expect(errList).NotTo(BeEmpty()) 1037 return 1038 } 1039 g.Expect(errList).To(BeEmpty()) 1040 }) 1041 } 1042 }