sigs.k8s.io/cluster-api@v1.6.3/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/pointer" 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: pointer.Int64(1), 46 }, 47 }, 48 }, 49 { 50 Name: "cpu", 51 Schema: clusterv1.VariableSchema{ 52 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 53 Type: "integer", 54 Minimum: pointer.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: pointer.Int64(1), 70 }, 71 }, 72 }, 73 { 74 Name: "validNumber", 75 Schema: clusterv1.VariableSchema{ 76 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 77 Type: "number", 78 Maximum: pointer.Int64(1), 79 }, 80 }, 81 }, 82 83 { 84 Name: "validVariable", 85 Schema: clusterv1.VariableSchema{ 86 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 87 Type: "string", 88 MinLength: pointer.Int64(1), 89 }, 90 }, 91 }, 92 { 93 Name: "location", 94 Schema: clusterv1.VariableSchema{ 95 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 96 Type: "string", 97 MinLength: pointer.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: pointer.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: pointer.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: pointer.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: pointer.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: pointer.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: pointer.Int64(1), 198 }, 199 }, 200 }, 201 wantErr: true, 202 }, 203 { 204 name: "Valid default value regular string", 205 clusterClassVariable: &clusterv1.ClusterClassVariable{ 206 Name: "var", 207 Required: true, 208 Schema: clusterv1.VariableSchema{ 209 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 210 Type: "string", 211 Default: &apiextensionsv1.JSON{ 212 Raw: []byte(`"defaultValue"`), 213 }, 214 }, 215 }, 216 }, 217 }, 218 { 219 name: "fail on default value with invalid JSON", 220 clusterClassVariable: &clusterv1.ClusterClassVariable{ 221 Name: "var", 222 Required: true, 223 Schema: clusterv1.VariableSchema{ 224 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 225 Type: "string", 226 Default: &apiextensionsv1.JSON{ 227 Raw: []byte(`"defaultValue": "value"`), // invalid JSON 228 }, 229 }, 230 }, 231 }, 232 wantErr: true, 233 }, 234 { 235 name: "Valid example value regular string", 236 clusterClassVariable: &clusterv1.ClusterClassVariable{ 237 Name: "var", 238 Required: true, 239 Schema: clusterv1.VariableSchema{ 240 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 241 Type: "string", 242 Example: &apiextensionsv1.JSON{ 243 Raw: []byte(`"exampleValue"`), 244 }, 245 }, 246 }, 247 }, 248 }, 249 { 250 name: "fail on example value with invalid JSON", 251 clusterClassVariable: &clusterv1.ClusterClassVariable{ 252 Name: "var", 253 Required: true, 254 Schema: clusterv1.VariableSchema{ 255 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 256 Type: "string", 257 Example: &apiextensionsv1.JSON{ 258 Raw: []byte(`"exampleValue": "value"`), // invalid JSON 259 }, 260 }, 261 }, 262 }, 263 wantErr: true, 264 }, 265 { 266 name: "Valid enum values", 267 clusterClassVariable: &clusterv1.ClusterClassVariable{ 268 Name: "var", 269 Required: true, 270 Schema: clusterv1.VariableSchema{ 271 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 272 Type: "string", 273 Enum: []apiextensionsv1.JSON{ 274 {Raw: []byte(`"enumValue1"`)}, 275 {Raw: []byte(`"enumValue2"`)}, 276 }, 277 }, 278 }, 279 }, 280 }, 281 { 282 name: "fail on enum value with invalid JSON", 283 clusterClassVariable: &clusterv1.ClusterClassVariable{ 284 Name: "var", 285 Required: true, 286 Schema: clusterv1.VariableSchema{ 287 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 288 Type: "string", 289 Enum: []apiextensionsv1.JSON{ 290 {Raw: []byte(`"defaultValue": "value"`)}, // invalid JSON 291 }, 292 }, 293 }, 294 }, 295 wantErr: true, 296 }, 297 { 298 name: "fail on variable type is null", 299 clusterClassVariable: &clusterv1.ClusterClassVariable{ 300 Name: "var", 301 Schema: clusterv1.VariableSchema{ 302 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 303 Type: "null", 304 }, 305 }, 306 }, 307 wantErr: true, 308 }, 309 { 310 name: "fail on variable type is not valid", 311 clusterClassVariable: &clusterv1.ClusterClassVariable{ 312 Name: "var", 313 Schema: clusterv1.VariableSchema{ 314 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 315 Type: "invalidVariableType", 316 }, 317 }, 318 }, 319 wantErr: true, 320 }, 321 { 322 name: "fail on variable type length zero", 323 clusterClassVariable: &clusterv1.ClusterClassVariable{ 324 Name: "var", 325 Schema: clusterv1.VariableSchema{ 326 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 327 Type: "", 328 }, 329 }, 330 }, 331 wantErr: true, 332 }, 333 { 334 name: "Valid object schema", 335 clusterClassVariable: &clusterv1.ClusterClassVariable{ 336 Name: "httpProxy", 337 Required: true, 338 Schema: clusterv1.VariableSchema{ 339 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 340 Type: "object", 341 Properties: map[string]clusterv1.JSONSchemaProps{ 342 "enabled": { 343 Type: "boolean", 344 Default: &apiextensionsv1.JSON{Raw: []byte(`false`)}, 345 }, 346 "url": { 347 Type: "string", 348 }, 349 "noProxy": { 350 Type: "string", 351 }, 352 }, 353 }, 354 }, 355 }, 356 }, 357 { 358 name: "fail on invalid object schema", 359 clusterClassVariable: &clusterv1.ClusterClassVariable{ 360 Name: "httpProxy", 361 Required: true, 362 Schema: clusterv1.VariableSchema{ 363 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 364 Type: "object", 365 Properties: map[string]clusterv1.JSONSchemaProps{ 366 "enabled": { 367 Type: "boolean", 368 Default: &apiextensionsv1.JSON{Raw: []byte(`false`)}, 369 }, 370 "url": { 371 Type: "string", 372 }, 373 "noProxy": { 374 Type: "invalidType", // invalid type. 375 }, 376 }, 377 }, 378 }, 379 }, 380 wantErr: true, 381 }, 382 { 383 name: "Valid map schema", 384 clusterClassVariable: &clusterv1.ClusterClassVariable{ 385 Name: "httpProxy", 386 Required: true, 387 Schema: clusterv1.VariableSchema{ 388 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 389 Type: "object", 390 AdditionalProperties: &clusterv1.JSONSchemaProps{ 391 Type: "object", 392 Properties: map[string]clusterv1.JSONSchemaProps{ 393 "enabled": { 394 Type: "boolean", 395 Default: &apiextensionsv1.JSON{Raw: []byte(`false`)}, 396 }, 397 "url": { 398 Type: "string", 399 }, 400 "noProxy": { 401 Type: "string", 402 }, 403 }, 404 }, 405 }, 406 }, 407 }, 408 }, 409 { 410 name: "fail on invalid map schema", 411 clusterClassVariable: &clusterv1.ClusterClassVariable{ 412 Name: "httpProxy", 413 Required: true, 414 Schema: clusterv1.VariableSchema{ 415 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 416 Type: "object", 417 AdditionalProperties: &clusterv1.JSONSchemaProps{ 418 Type: "object", 419 Properties: map[string]clusterv1.JSONSchemaProps{ 420 "enabled": { 421 Type: "boolean", 422 Default: &apiextensionsv1.JSON{Raw: []byte(`false`)}, 423 }, 424 "url": { 425 Type: "string", 426 }, 427 "noProxy": { 428 Type: "invalidType", // invalid type. 429 }, 430 }, 431 }, 432 }, 433 }, 434 }, 435 wantErr: true, 436 }, 437 { 438 name: "fail on object (properties) and map (additionalProperties) both set", 439 clusterClassVariable: &clusterv1.ClusterClassVariable{ 440 Name: "httpProxy", 441 Required: true, 442 Schema: clusterv1.VariableSchema{ 443 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 444 Type: "object", 445 AdditionalProperties: &clusterv1.JSONSchemaProps{ 446 Type: "object", 447 Properties: map[string]clusterv1.JSONSchemaProps{ 448 "enabled": { 449 Type: "boolean", 450 Default: &apiextensionsv1.JSON{Raw: []byte(`false`)}, 451 }, 452 }, 453 }, 454 Properties: map[string]clusterv1.JSONSchemaProps{ 455 "enabled": { 456 Type: "boolean", 457 Default: &apiextensionsv1.JSON{Raw: []byte(`false`)}, 458 }, 459 }, 460 }, 461 }, 462 }, 463 wantErr: true, 464 }, 465 { 466 name: "Valid array schema", 467 clusterClassVariable: &clusterv1.ClusterClassVariable{ 468 Name: "arrayVariable", 469 Required: true, 470 Schema: clusterv1.VariableSchema{ 471 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 472 Type: "array", 473 Items: &clusterv1.JSONSchemaProps{ 474 Type: "boolean", 475 Default: &apiextensionsv1.JSON{Raw: []byte(`false`)}, 476 }, 477 }, 478 }, 479 }, 480 }, 481 { 482 name: "fail on invalid array schema", 483 clusterClassVariable: &clusterv1.ClusterClassVariable{ 484 Name: "arrayVariable", 485 Schema: clusterv1.VariableSchema{ 486 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 487 Type: "array", 488 Items: &clusterv1.JSONSchemaProps{ 489 Type: "string", 490 Default: &apiextensionsv1.JSON{Raw: []byte(`invalidString`)}, // missing quotes. 491 }, 492 }, 493 }, 494 }, 495 wantErr: true, 496 }, 497 { 498 name: "pass on variable with required set true with a default defined", 499 clusterClassVariable: &clusterv1.ClusterClassVariable{ 500 Name: "var", 501 Required: true, 502 Schema: clusterv1.VariableSchema{ 503 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 504 Type: "string", 505 Default: &apiextensionsv1.JSON{Raw: []byte(`"defaultValue"`)}, 506 }, 507 }, 508 }, 509 }, 510 { 511 name: "pass on variable with a default that is valid by the given schema", 512 clusterClassVariable: &clusterv1.ClusterClassVariable{ 513 Name: "var", 514 Schema: clusterv1.VariableSchema{ 515 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 516 Type: "string", 517 MaxLength: pointer.Int64(6), 518 Default: &apiextensionsv1.JSON{Raw: []byte(`"short"`)}, 519 }, 520 }, 521 }, 522 }, 523 { 524 name: "fail on variable with a default that is invalid by the given schema", 525 clusterClassVariable: &clusterv1.ClusterClassVariable{ 526 Name: "var", 527 Schema: clusterv1.VariableSchema{ 528 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 529 Type: "string", 530 MaxLength: pointer.Int64(6), 531 Default: &apiextensionsv1.JSON{Raw: []byte(`"veryLongValueIsInvalid"`)}, 532 }, 533 }, 534 }, 535 wantErr: true, 536 }, 537 { 538 name: "pass if variable is an object with default value valid by the given schema", 539 clusterClassVariable: &clusterv1.ClusterClassVariable{ 540 Name: "var", 541 Schema: clusterv1.VariableSchema{ 542 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 543 Type: "object", 544 Properties: map[string]clusterv1.JSONSchemaProps{ 545 "spec": { 546 Type: "object", 547 Properties: map[string]clusterv1.JSONSchemaProps{ 548 "replicas": { 549 Type: "integer", 550 Default: &apiextensionsv1.JSON{Raw: []byte(`100`)}, 551 Minimum: pointer.Int64(1), 552 }, 553 }, 554 }, 555 }, 556 }, 557 }, 558 }, 559 }, 560 { 561 name: "fail if variable is an object with default value invalidated by the given schema", 562 clusterClassVariable: &clusterv1.ClusterClassVariable{ 563 Name: "var", 564 Schema: clusterv1.VariableSchema{ 565 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 566 Type: "object", 567 Properties: map[string]clusterv1.JSONSchemaProps{ 568 "spec": { 569 Type: "object", 570 Properties: map[string]clusterv1.JSONSchemaProps{ 571 "replicas": { 572 Type: "integer", 573 Default: &apiextensionsv1.JSON{Raw: []byte(`-100`)}, 574 Minimum: pointer.Int64(1), 575 }, 576 }, 577 }, 578 }, 579 }, 580 }, 581 }, 582 wantErr: true, 583 }, 584 { 585 name: "fail if variable is an object with a top level default value invalidated by the given schema", 586 clusterClassVariable: &clusterv1.ClusterClassVariable{ 587 Name: "var", 588 Schema: clusterv1.VariableSchema{ 589 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 590 Type: "object", 591 Default: &apiextensionsv1.JSON{ 592 Raw: []byte(`{"spec":{"replicas": -100}}`), 593 }, 594 Properties: map[string]clusterv1.JSONSchemaProps{ 595 "spec": { 596 Type: "object", 597 Properties: map[string]clusterv1.JSONSchemaProps{ 598 "replicas": { 599 Type: "integer", 600 Minimum: pointer.Int64(1), 601 }, 602 }, 603 }, 604 }, 605 }, 606 }, 607 }, 608 wantErr: true, 609 }, 610 { 611 name: "pass if variable is an object with a top level default value valid by the given schema", 612 clusterClassVariable: &clusterv1.ClusterClassVariable{ 613 Name: "var", 614 Schema: clusterv1.VariableSchema{ 615 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 616 Type: "object", 617 Default: &apiextensionsv1.JSON{ 618 Raw: []byte(`{"spec":{"replicas": 100}}`), 619 }, 620 Properties: map[string]clusterv1.JSONSchemaProps{ 621 "spec": { 622 Type: "object", 623 Properties: map[string]clusterv1.JSONSchemaProps{ 624 "replicas": { 625 Type: "integer", 626 Minimum: pointer.Int64(1), 627 }, 628 }, 629 }, 630 }, 631 }, 632 }, 633 }, 634 }, 635 { 636 name: "fail if field is required below properties and sets a default that misses the field", 637 clusterClassVariable: &clusterv1.ClusterClassVariable{ 638 Name: "var", 639 Schema: clusterv1.VariableSchema{ 640 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 641 Type: "object", 642 Properties: map[string]clusterv1.JSONSchemaProps{ 643 "spec": { 644 Type: "object", 645 Required: []string{"replicas"}, 646 Default: &apiextensionsv1.JSON{ 647 // replicas missing results in failure 648 Raw: []byte(`{"value": 100}`), 649 }, 650 Properties: map[string]clusterv1.JSONSchemaProps{ 651 "replicas": { 652 Type: "integer", 653 Default: &apiextensionsv1.JSON{Raw: []byte(`100`)}, 654 Minimum: pointer.Int64(1), 655 }, 656 "value": { 657 Type: "integer", 658 Default: &apiextensionsv1.JSON{Raw: []byte(`100`)}, 659 Minimum: pointer.Int64(1), 660 }, 661 }, 662 }, 663 }, 664 }, 665 }, 666 }, 667 wantErr: true, 668 }, 669 { 670 name: "pass if field is required below properties and sets a default", 671 clusterClassVariable: &clusterv1.ClusterClassVariable{ 672 Name: "var", 673 Schema: clusterv1.VariableSchema{ 674 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 675 Type: "object", 676 Properties: map[string]clusterv1.JSONSchemaProps{ 677 "spec": { 678 Type: "object", 679 Required: []string{"replicas"}, 680 Default: &apiextensionsv1.JSON{ 681 // replicas is set here so the `required` property is met. 682 Raw: []byte(`{"replicas": 100}`), 683 }, 684 Properties: map[string]clusterv1.JSONSchemaProps{ 685 "replicas": { 686 Type: "integer", 687 Default: &apiextensionsv1.JSON{Raw: []byte(`100`)}, 688 Minimum: pointer.Int64(1), 689 }, 690 }, 691 }, 692 }, 693 }, 694 }, 695 }, 696 }, 697 698 { 699 name: "pass on variable with an example that is valid by the given schema", 700 clusterClassVariable: &clusterv1.ClusterClassVariable{ 701 Name: "var", 702 Schema: clusterv1.VariableSchema{ 703 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 704 Type: "string", 705 MaxLength: pointer.Int64(6), 706 Example: &apiextensionsv1.JSON{Raw: []byte(`"short"`)}, 707 }, 708 }, 709 }, 710 }, 711 { 712 name: "fail on variable with an example that is invalid by the given schema", 713 clusterClassVariable: &clusterv1.ClusterClassVariable{ 714 Name: "var", 715 Schema: clusterv1.VariableSchema{ 716 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 717 Type: "string", 718 MaxLength: pointer.Int64(6), 719 Example: &apiextensionsv1.JSON{Raw: []byte(`"veryLongValueIsInvalid"`)}, 720 }, 721 }, 722 }, 723 wantErr: true, 724 }, 725 { 726 name: "pass if variable is an object with an example valid by the given schema", 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 Properties: map[string]clusterv1.JSONSchemaProps{ 736 "replicas": { 737 Type: "integer", 738 Minimum: pointer.Int64(0), 739 Example: &apiextensionsv1.JSON{Raw: []byte(`100`)}, 740 }, 741 }, 742 }, 743 }, 744 }, 745 }, 746 }, 747 }, 748 { 749 name: "fail if variable is an object with an example invalidated by the given schema", 750 clusterClassVariable: &clusterv1.ClusterClassVariable{ 751 Name: "var", 752 Schema: clusterv1.VariableSchema{ 753 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 754 Type: "object", 755 Properties: map[string]clusterv1.JSONSchemaProps{ 756 "spec": { 757 Type: "object", 758 Properties: map[string]clusterv1.JSONSchemaProps{ 759 "replicas": { 760 Type: "integer", 761 Minimum: pointer.Int64(0), 762 Example: &apiextensionsv1.JSON{Raw: []byte(`-100`)}, 763 }, 764 }, 765 }, 766 }, 767 }, 768 }, 769 }, 770 wantErr: true, 771 }, 772 { 773 name: "fail if variable is an object with a top level example value invalidated by the given schema", 774 clusterClassVariable: &clusterv1.ClusterClassVariable{ 775 Name: "var", 776 Schema: clusterv1.VariableSchema{ 777 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 778 Type: "object", 779 Example: &apiextensionsv1.JSON{ 780 Raw: []byte(`{"spec":{"replicas": -100}}`), 781 }, 782 Properties: map[string]clusterv1.JSONSchemaProps{ 783 "spec": { 784 Type: "object", 785 Properties: map[string]clusterv1.JSONSchemaProps{ 786 "replicas": { 787 Type: "integer", 788 Minimum: pointer.Int64(1), 789 }, 790 }, 791 }, 792 }, 793 }, 794 }, 795 }, 796 wantErr: true, 797 }, 798 { 799 name: "pass if variable is an object with a top level default value valid by the given schema", 800 clusterClassVariable: &clusterv1.ClusterClassVariable{ 801 Name: "var", 802 Schema: clusterv1.VariableSchema{ 803 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 804 Type: "object", 805 Example: &apiextensionsv1.JSON{ 806 Raw: []byte(`{"spec":{"replicas": 100}}`), 807 }, 808 Properties: map[string]clusterv1.JSONSchemaProps{ 809 "spec": { 810 Type: "object", 811 Properties: map[string]clusterv1.JSONSchemaProps{ 812 "replicas": { 813 Type: "integer", 814 Minimum: pointer.Int64(1), 815 }, 816 }, 817 }, 818 }, 819 }, 820 }, 821 }, 822 }, 823 { 824 name: "pass on variable with an enum with all variables valid by the given schema", 825 clusterClassVariable: &clusterv1.ClusterClassVariable{ 826 Name: "var", 827 Schema: clusterv1.VariableSchema{ 828 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 829 Type: "string", 830 MaxLength: pointer.Int64(6), 831 Enum: []apiextensionsv1.JSON{ 832 {Raw: []byte(`"short1"`)}, 833 {Raw: []byte(`"short2"`)}, 834 }, 835 }, 836 }, 837 }, 838 }, 839 { 840 name: "fail on variable with an enum with a value that is invalid by the given schema", 841 clusterClassVariable: &clusterv1.ClusterClassVariable{ 842 Name: "var", 843 Schema: clusterv1.VariableSchema{ 844 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 845 Type: "string", 846 MaxLength: pointer.Int64(6), 847 Enum: []apiextensionsv1.JSON{ 848 {Raw: []byte(`"veryLongValueIsInvalid"`)}, 849 {Raw: []byte(`"short"`)}, 850 }, 851 }, 852 }, 853 }, 854 wantErr: true, 855 }, 856 { 857 name: "pass if variable is an object with an enum value that is valid by the given schema", 858 clusterClassVariable: &clusterv1.ClusterClassVariable{ 859 Name: "var", 860 Schema: clusterv1.VariableSchema{ 861 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 862 Type: "object", 863 Properties: map[string]clusterv1.JSONSchemaProps{ 864 "spec": { 865 Type: "object", 866 Properties: map[string]clusterv1.JSONSchemaProps{ 867 "replicas": { 868 Type: "integer", 869 Minimum: pointer.Int64(0), 870 Enum: []apiextensionsv1.JSON{ 871 {Raw: []byte(`100`)}, 872 {Raw: []byte(`5`)}, 873 }, 874 }, 875 }, 876 }, 877 }, 878 }, 879 }, 880 }, 881 }, 882 { 883 name: "fail if variable is an object with an enum value invalidated by the given schema", 884 clusterClassVariable: &clusterv1.ClusterClassVariable{ 885 Name: "var", 886 Schema: clusterv1.VariableSchema{ 887 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 888 Type: "object", 889 Properties: map[string]clusterv1.JSONSchemaProps{ 890 "spec": { 891 Type: "object", 892 Properties: map[string]clusterv1.JSONSchemaProps{ 893 "replicas": { 894 Type: "integer", 895 Minimum: pointer.Int64(0), 896 Enum: []apiextensionsv1.JSON{ 897 {Raw: []byte(`100`)}, 898 {Raw: []byte(`-100`)}, 899 }, 900 }, 901 }, 902 }, 903 }, 904 }, 905 }, 906 }, 907 wantErr: true, 908 }, 909 { 910 name: "fail if variable is an object with a top level enum value invalidated by the given schema", 911 clusterClassVariable: &clusterv1.ClusterClassVariable{ 912 Name: "var", 913 Schema: clusterv1.VariableSchema{ 914 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 915 Type: "object", 916 Enum: []apiextensionsv1.JSON{ 917 { 918 Raw: []byte(`{"spec":{"replicas": 100}}`), 919 }, 920 { 921 Raw: []byte(`{"spec":{"replicas": -100}}`), 922 }, 923 }, 924 Properties: map[string]clusterv1.JSONSchemaProps{ 925 "spec": { 926 Type: "object", 927 Properties: map[string]clusterv1.JSONSchemaProps{ 928 "replicas": { 929 Type: "integer", 930 Minimum: pointer.Int64(1), 931 }, 932 }, 933 }, 934 }, 935 }, 936 }, 937 }, 938 wantErr: true, 939 }, 940 { 941 name: "pass if variable is an object with a top level enum value that is valid by the given schema", 942 clusterClassVariable: &clusterv1.ClusterClassVariable{ 943 Name: "var", 944 Schema: clusterv1.VariableSchema{ 945 OpenAPIV3Schema: clusterv1.JSONSchemaProps{ 946 Type: "object", 947 Enum: []apiextensionsv1.JSON{ 948 { 949 Raw: []byte(`{"spec":{"replicas": 100}}`), 950 }, 951 { 952 Raw: []byte(`{"spec":{"replicas": 200}}`), 953 }, 954 }, 955 Properties: map[string]clusterv1.JSONSchemaProps{ 956 "spec": { 957 Type: "object", 958 Properties: map[string]clusterv1.JSONSchemaProps{ 959 "replicas": { 960 Type: "integer", 961 Minimum: pointer.Int64(1), 962 }, 963 }, 964 }, 965 }, 966 }, 967 }, 968 }, 969 }, 970 } 971 for _, tt := range tests { 972 t.Run(tt.name, func(t *testing.T) { 973 g := NewWithT(t) 974 975 errList := validateClusterClassVariable(context.TODO(), 976 tt.clusterClassVariable, 977 field.NewPath("spec", "variables").Index(0)) 978 979 if tt.wantErr { 980 g.Expect(errList).NotTo(BeEmpty()) 981 return 982 } 983 g.Expect(errList).To(BeEmpty()) 984 }) 985 } 986 }