sigs.k8s.io/cluster-api-provider-aws@v1.5.5/api/v1beta1/awscluster_webhook_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 v1beta1 18 19 import ( 20 "context" 21 "strings" 22 "testing" 23 "time" 24 25 "github.com/aws/aws-sdk-go/aws" 26 . "github.com/onsi/gomega" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 utilfeature "k8s.io/component-base/featuregate/testing" 29 "sigs.k8s.io/controller-runtime/pkg/client" 30 31 "sigs.k8s.io/cluster-api-provider-aws/feature" 32 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 33 utildefaulting "sigs.k8s.io/cluster-api/util/defaulting" 34 ) 35 36 func TestAWSClusterDefault(t *testing.T) { 37 cluster := &AWSCluster{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"}} 38 t.Run("for AWSCluster", utildefaulting.DefaultValidateTest(cluster)) 39 cluster.Default() 40 g := NewWithT(t) 41 g.Expect(cluster.Spec.IdentityRef).NotTo(BeNil()) 42 } 43 44 func TestAWSCluster_ValidateCreate(t *testing.T) { 45 unsupportedIncorrectScheme := ClassicELBScheme("any-other-scheme") 46 47 tests := []struct { 48 name string 49 cluster *AWSCluster 50 wantErr bool 51 expect func(g *WithT, res *AWSLoadBalancerSpec) 52 }{ 53 // The SSHKeyName tests were moved to sshkeyname_test.go 54 { 55 name: "Default nil scheme to `internet-facing`", 56 cluster: &AWSCluster{ 57 Spec: AWSClusterSpec{}, 58 }, 59 expect: func(g *WithT, res *AWSLoadBalancerSpec) { 60 g.Expect(res.Scheme.String(), ClassicELBSchemeInternetFacing.String()) 61 }, 62 wantErr: false, 63 }, 64 { 65 name: "Internet-facing ELB scheme is defaulted to internet-facing during creation", 66 cluster: &AWSCluster{ 67 Spec: AWSClusterSpec{ 68 ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{Scheme: &ClassicELBSchemeIncorrectInternetFacing}, 69 }, 70 }, 71 expect: func(g *WithT, res *AWSLoadBalancerSpec) { 72 g.Expect(res.Scheme.String(), ClassicELBSchemeInternetFacing.String()) 73 }, 74 wantErr: false, 75 }, 76 { 77 name: "Supported schemes are 'internet-facing, Internet-facing, internal, or nil', rest will be rejected", 78 cluster: &AWSCluster{ 79 Spec: AWSClusterSpec{ 80 ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{Scheme: &unsupportedIncorrectScheme}, 81 }, 82 }, 83 wantErr: true, 84 }, 85 { 86 name: "Invalid tags are rejected", 87 cluster: &AWSCluster{ 88 Spec: AWSClusterSpec{ 89 AdditionalTags: Tags{ 90 "key-1": "value-1", 91 "": "value-2", 92 strings.Repeat("CAPI", 33): "value-3", 93 "key-4": strings.Repeat("CAPI", 65), 94 }, 95 }, 96 }, 97 wantErr: true, 98 }, 99 { 100 name: "accepts bucket name with acceptable characters", 101 cluster: &AWSCluster{ 102 Spec: AWSClusterSpec{ 103 S3Bucket: &S3Bucket{ 104 Name: "abcdefghijklmnoprstuwxyz-0123456789", 105 ControlPlaneIAMInstanceProfile: "control-plane.cluster-api-provider-aws.sigs.k8s.io", 106 NodesIAMInstanceProfiles: []string{"nodes.cluster-api-provider-aws.sigs.k8s.io"}, 107 }, 108 }, 109 }, 110 }, 111 { 112 name: "rejects empty bucket name", 113 cluster: &AWSCluster{ 114 Spec: AWSClusterSpec{ 115 S3Bucket: &S3Bucket{}, 116 }, 117 }, 118 wantErr: true, 119 }, 120 { 121 name: "rejects bucket name shorter than 3 characters", 122 cluster: &AWSCluster{ 123 Spec: AWSClusterSpec{ 124 S3Bucket: &S3Bucket{ 125 Name: "fo", 126 }, 127 }, 128 }, 129 wantErr: true, 130 }, 131 { 132 name: "rejects bucket name longer than 63 characters", 133 cluster: &AWSCluster{ 134 Spec: AWSClusterSpec{ 135 S3Bucket: &S3Bucket{ 136 Name: strings.Repeat("a", 64), 137 }, 138 }, 139 }, 140 wantErr: true, 141 }, 142 { 143 name: "rejects bucket name starting with not letter or number", 144 cluster: &AWSCluster{ 145 Spec: AWSClusterSpec{ 146 S3Bucket: &S3Bucket{ 147 Name: "-foo", 148 }, 149 }, 150 }, 151 wantErr: true, 152 }, 153 { 154 name: "rejects bucket name ending with not letter or number", 155 cluster: &AWSCluster{ 156 Spec: AWSClusterSpec{ 157 S3Bucket: &S3Bucket{ 158 Name: "foo-", 159 }, 160 }, 161 }, 162 wantErr: true, 163 }, 164 { 165 name: "rejects bucket name formatted as IP address", 166 cluster: &AWSCluster{ 167 Spec: AWSClusterSpec{ 168 S3Bucket: &S3Bucket{ 169 Name: "8.8.8.8", 170 }, 171 }, 172 }, 173 wantErr: true, 174 }, 175 { 176 name: "requires bucket control plane IAM instance profile to be not empty", 177 cluster: &AWSCluster{ 178 Spec: AWSClusterSpec{ 179 S3Bucket: &S3Bucket{ 180 Name: "foo", 181 ControlPlaneIAMInstanceProfile: "", 182 }, 183 }, 184 }, 185 wantErr: true, 186 }, 187 { 188 name: "requires at least one bucket node IAM instance profile", 189 cluster: &AWSCluster{ 190 Spec: AWSClusterSpec{ 191 S3Bucket: &S3Bucket{ 192 Name: "foo", 193 ControlPlaneIAMInstanceProfile: "foo", 194 }, 195 }, 196 }, 197 wantErr: true, 198 }, 199 { 200 name: "requires all bucket node IAM instance profiles to be not empty", 201 cluster: &AWSCluster{ 202 Spec: AWSClusterSpec{ 203 S3Bucket: &S3Bucket{ 204 Name: "foo", 205 ControlPlaneIAMInstanceProfile: "foo", 206 NodesIAMInstanceProfiles: []string{""}, 207 }, 208 }, 209 }, 210 wantErr: true, 211 }, 212 { 213 name: "does not return error when all IAM instance profiles are populated", 214 cluster: &AWSCluster{ 215 Spec: AWSClusterSpec{ 216 S3Bucket: &S3Bucket{ 217 Name: "foo", 218 ControlPlaneIAMInstanceProfile: "foo", 219 NodesIAMInstanceProfiles: []string{"bar"}, 220 }, 221 }, 222 }, 223 wantErr: false, 224 }, 225 } 226 for _, tt := range tests { 227 t.Run(tt.name, func(t *testing.T) { 228 defer utilfeature.SetFeatureGateDuringTest(t, feature.Gates, feature.BootstrapFormatIgnition, true)() 229 230 cluster := tt.cluster.DeepCopy() 231 cluster.ObjectMeta = metav1.ObjectMeta{ 232 GenerateName: "cluster-", 233 Namespace: "default", 234 } 235 ctx := context.TODO() 236 if err := testEnv.Create(ctx, cluster); (err != nil) != tt.wantErr { 237 t.Errorf("ValidateCreate() error = %v, wantErr %v", err, tt.wantErr) 238 } 239 240 if tt.wantErr { 241 return 242 } 243 244 c := &AWSCluster{} 245 key := client.ObjectKey{ 246 Name: cluster.Name, 247 Namespace: "default", 248 } 249 250 g := NewWithT(t) 251 g.Eventually(func() bool { 252 err := testEnv.Get(ctx, key, c) 253 return err == nil 254 }, 10*time.Second).Should(Equal(true)) 255 256 if tt.expect != nil { 257 tt.expect(g, c.Spec.ControlPlaneLoadBalancer) 258 } 259 }) 260 } 261 } 262 263 func TestAWSCluster_ValidateUpdate(t *testing.T) { 264 tests := []struct { 265 name string 266 oldCluster *AWSCluster 267 newCluster *AWSCluster 268 wantErr bool 269 }{ 270 { 271 name: "region is immutable", 272 oldCluster: &AWSCluster{ 273 Spec: AWSClusterSpec{ 274 Region: "us-east-1", 275 }, 276 }, 277 newCluster: &AWSCluster{ 278 Spec: AWSClusterSpec{ 279 Region: "us-east-2", 280 }, 281 }, 282 wantErr: true, 283 }, 284 { 285 name: "controlPlaneLoadBalancer name is immutable", 286 oldCluster: &AWSCluster{ 287 Spec: AWSClusterSpec{ 288 ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{ 289 Name: aws.String("old-apiserver"), 290 }, 291 }, 292 }, 293 newCluster: &AWSCluster{ 294 Spec: AWSClusterSpec{ 295 ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{ 296 Name: aws.String("new-apiserver"), 297 }, 298 }, 299 }, 300 wantErr: true, 301 }, 302 { 303 name: "controlPlaneLoadBalancer name is immutable, even if it is nil", 304 oldCluster: &AWSCluster{ 305 Spec: AWSClusterSpec{ 306 ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{ 307 Name: nil, 308 }, 309 }, 310 }, 311 newCluster: &AWSCluster{ 312 Spec: AWSClusterSpec{ 313 ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{ 314 Name: aws.String("example-apiserver"), 315 }, 316 }, 317 }, 318 wantErr: true, 319 }, 320 { 321 name: "controlPlaneLoadBalancer scheme is immutable", 322 oldCluster: &AWSCluster{ 323 Spec: AWSClusterSpec{ 324 ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{ 325 Scheme: &ClassicELBSchemeInternal, 326 }, 327 }, 328 }, 329 newCluster: &AWSCluster{ 330 Spec: AWSClusterSpec{ 331 ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{ 332 Scheme: &ClassicELBSchemeInternetFacing, 333 }, 334 }, 335 }, 336 wantErr: true, 337 }, 338 { 339 name: "controlPlaneLoadBalancer scheme is immutable when left empty", 340 oldCluster: &AWSCluster{ 341 Spec: AWSClusterSpec{}, 342 }, 343 newCluster: &AWSCluster{ 344 Spec: AWSClusterSpec{ 345 ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{ 346 Scheme: &ClassicELBSchemeInternal, 347 }, 348 }, 349 }, 350 wantErr: true, 351 }, 352 { 353 name: "controlPlaneLoadBalancer scheme can be set to default when left empty", 354 oldCluster: &AWSCluster{ 355 Spec: AWSClusterSpec{}, 356 }, 357 newCluster: &AWSCluster{ 358 Spec: AWSClusterSpec{ 359 ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{ 360 Scheme: &ClassicELBSchemeInternetFacing, 361 }, 362 }, 363 }, 364 wantErr: false, 365 }, 366 { 367 name: "controlPlaneLoadBalancer crossZoneLoadBalancer is mutable", 368 oldCluster: &AWSCluster{ 369 Spec: AWSClusterSpec{ 370 ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{ 371 CrossZoneLoadBalancing: false, 372 }, 373 }, 374 }, 375 newCluster: &AWSCluster{ 376 Spec: AWSClusterSpec{ 377 ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{ 378 CrossZoneLoadBalancing: true, 379 }, 380 }, 381 }, 382 wantErr: false, 383 }, 384 { 385 name: "controlPlaneEndpoint is immutable", 386 oldCluster: &AWSCluster{ 387 Spec: AWSClusterSpec{ 388 ControlPlaneEndpoint: clusterv1.APIEndpoint{ 389 Host: "example.com", 390 Port: int32(8000), 391 }, 392 }, 393 }, 394 newCluster: &AWSCluster{ 395 Spec: AWSClusterSpec{ 396 ControlPlaneEndpoint: clusterv1.APIEndpoint{ 397 Host: "foo.example.com", 398 Port: int32(9000), 399 }, 400 }, 401 }, 402 wantErr: true, 403 }, 404 { 405 name: "controlPlaneEndpoint can be updated if it is empty", 406 oldCluster: &AWSCluster{ 407 Spec: AWSClusterSpec{ 408 ControlPlaneEndpoint: clusterv1.APIEndpoint{}, 409 }, 410 }, 411 newCluster: &AWSCluster{ 412 Spec: AWSClusterSpec{ 413 ControlPlaneEndpoint: clusterv1.APIEndpoint{ 414 Host: "example.com", 415 Port: int32(8000), 416 }, 417 }, 418 }, 419 wantErr: false, 420 }, 421 { 422 name: "removal of externally managed annotation is not allowed", 423 oldCluster: &AWSCluster{ 424 ObjectMeta: metav1.ObjectMeta{ 425 Annotations: map[string]string{clusterv1.ManagedByAnnotation: ""}, 426 }, 427 }, 428 newCluster: &AWSCluster{}, 429 wantErr: true, 430 }, 431 { 432 name: "adding externally managed annotation is allowed", 433 oldCluster: &AWSCluster{}, 434 newCluster: &AWSCluster{ 435 ObjectMeta: metav1.ObjectMeta{ 436 Annotations: map[string]string{clusterv1.ManagedByAnnotation: ""}, 437 }, 438 }, 439 wantErr: false, 440 }, 441 { 442 name: "VPC id is immutable cannot be emptied once set", 443 oldCluster: &AWSCluster{ 444 Spec: AWSClusterSpec{ 445 NetworkSpec: NetworkSpec{ 446 VPC: VPCSpec{ID: "managed-or-unmanaged-vpc"}, 447 }, 448 }, 449 }, 450 newCluster: &AWSCluster{ 451 Spec: AWSClusterSpec{}, 452 }, 453 wantErr: true, 454 }, 455 { 456 name: "VPC id is immutable, cannot be set to a different value once set", 457 oldCluster: &AWSCluster{ 458 Spec: AWSClusterSpec{ 459 NetworkSpec: NetworkSpec{ 460 VPC: VPCSpec{ID: "managed-or-unmanaged-vpc"}, 461 }, 462 }, 463 }, 464 newCluster: &AWSCluster{ 465 Spec: AWSClusterSpec{ 466 NetworkSpec: NetworkSpec{ 467 VPC: VPCSpec{ID: "a-new-vpc"}, 468 }, 469 }, 470 }, 471 wantErr: true, 472 }, 473 { 474 name: "invalid keys are not accepted during update", 475 oldCluster: &AWSCluster{ 476 Spec: AWSClusterSpec{ 477 AdditionalTags: Tags{ 478 "key-1": "value-1", 479 "key-2": "value-2", 480 }, 481 }, 482 }, 483 newCluster: &AWSCluster{ 484 Spec: AWSClusterSpec{ 485 AdditionalTags: Tags{ 486 "key-1": "value-1", 487 "": "value-2", 488 strings.Repeat("CAPI", 33): "value-3", 489 "key-4": strings.Repeat("CAPI", 65), 490 }, 491 }, 492 }, 493 wantErr: true, 494 }, 495 { 496 name: "Should fail if controlPlaneLoadBalancer healthcheckprotocol is updated", 497 oldCluster: &AWSCluster{ 498 Spec: AWSClusterSpec{ 499 ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{ 500 HealthCheckProtocol: &ClassicELBProtocolTCP, 501 }, 502 }, 503 }, 504 newCluster: &AWSCluster{ 505 Spec: AWSClusterSpec{ 506 ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{ 507 HealthCheckProtocol: &ClassicELBProtocolSSL, 508 }, 509 }, 510 }, 511 wantErr: true, 512 }, 513 { 514 name: "Should pass if controlPlaneLoadBalancer healthcheckprotocol is same after update", 515 oldCluster: &AWSCluster{ 516 Spec: AWSClusterSpec{ 517 ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{ 518 HealthCheckProtocol: &ClassicELBProtocolTCP, 519 }, 520 }, 521 }, 522 newCluster: &AWSCluster{ 523 Spec: AWSClusterSpec{ 524 ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{ 525 HealthCheckProtocol: &ClassicELBProtocolTCP, 526 }, 527 }, 528 }, 529 wantErr: false, 530 }, 531 { 532 name: "Should fail if controlPlaneLoadBalancer healthcheckprotocol is changed to non-default if it was not set before update", 533 oldCluster: &AWSCluster{ 534 Spec: AWSClusterSpec{}, 535 }, 536 newCluster: &AWSCluster{ 537 Spec: AWSClusterSpec{ 538 ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{ 539 HealthCheckProtocol: &ClassicELBProtocolTCP, 540 }, 541 }, 542 }, 543 wantErr: true, 544 }, 545 } 546 for _, tt := range tests { 547 t.Run(tt.name, func(t *testing.T) { 548 ctx := context.TODO() 549 cluster := tt.oldCluster.DeepCopy() 550 cluster.ObjectMeta.GenerateName = "cluster-" 551 cluster.ObjectMeta.Namespace = "default" 552 553 if err := testEnv.Create(ctx, cluster); err != nil { 554 t.Errorf("failed to create cluster: %v", err) 555 } 556 cluster.ObjectMeta.Annotations = tt.newCluster.Annotations 557 cluster.Spec = tt.newCluster.Spec 558 if err := testEnv.Update(ctx, cluster); (err != nil) != tt.wantErr { 559 t.Errorf("ValidateUpdate() error = %v, wantErr %v", err, tt.wantErr) 560 } 561 }, 562 ) 563 } 564 } 565 566 func TestAWSCluster_DefaultCNIIngressRules(t *testing.T) { 567 AZUsageLimit := 3 568 defaultVPCSpec := VPCSpec{ 569 AvailabilityZoneUsageLimit: &AZUsageLimit, 570 AvailabilityZoneSelection: &AZSelectionSchemeOrdered, 571 } 572 g := NewWithT(t) 573 tests := []struct { 574 name string 575 beforeCluster *AWSCluster 576 afterCluster *AWSCluster 577 }{ 578 { 579 name: "CNI ingressRules are updated cni spec undefined", 580 beforeCluster: &AWSCluster{ 581 Spec: AWSClusterSpec{}, 582 }, 583 afterCluster: &AWSCluster{ 584 Spec: AWSClusterSpec{ 585 NetworkSpec: NetworkSpec{ 586 VPC: defaultVPCSpec, 587 CNI: &CNISpec{ 588 CNIIngressRules: CNIIngressRules{ 589 { 590 Description: "bgp (calico)", 591 Protocol: SecurityGroupProtocolTCP, 592 FromPort: 179, 593 ToPort: 179, 594 }, 595 { 596 Description: "IP-in-IP (calico)", 597 Protocol: SecurityGroupProtocolIPinIP, 598 FromPort: -1, 599 ToPort: 65535, 600 }, 601 }, 602 }, 603 }, 604 }, 605 }, 606 }, 607 { 608 name: "CNIIngressRules are not added for empty CNISpec", 609 beforeCluster: &AWSCluster{ 610 Spec: AWSClusterSpec{ 611 NetworkSpec: NetworkSpec{ 612 VPC: defaultVPCSpec, 613 CNI: &CNISpec{}, 614 }, 615 }, 616 }, 617 afterCluster: &AWSCluster{ 618 Spec: AWSClusterSpec{ 619 NetworkSpec: NetworkSpec{ 620 VPC: defaultVPCSpec, 621 CNI: &CNISpec{}, 622 }, 623 }, 624 }, 625 }, 626 { 627 name: "CNI ingressRules are unmodified when they exist", 628 beforeCluster: &AWSCluster{ 629 Spec: AWSClusterSpec{ 630 NetworkSpec: NetworkSpec{ 631 VPC: defaultVPCSpec, 632 CNI: &CNISpec{ 633 CNIIngressRules: CNIIngressRules{ 634 { 635 Description: "Antrea 1", 636 Protocol: SecurityGroupProtocolTCP, 637 FromPort: 10349, 638 ToPort: 10349, 639 }, 640 }, 641 }, 642 }, 643 }, 644 }, 645 afterCluster: &AWSCluster{ 646 Spec: AWSClusterSpec{ 647 NetworkSpec: NetworkSpec{ 648 VPC: defaultVPCSpec, 649 CNI: &CNISpec{ 650 CNIIngressRules: CNIIngressRules{ 651 { 652 Description: "Antrea 1", 653 Protocol: SecurityGroupProtocolTCP, 654 FromPort: 10349, 655 ToPort: 10349, 656 }, 657 }, 658 }, 659 }, 660 }, 661 }, 662 }, 663 } 664 665 for _, tt := range tests { 666 t.Run(tt.name, func(t *testing.T) { 667 ctx := context.TODO() 668 cluster := tt.beforeCluster.DeepCopy() 669 cluster.ObjectMeta = metav1.ObjectMeta{ 670 GenerateName: "cluster-", 671 Namespace: "default", 672 } 673 g.Expect(testEnv.Create(ctx, cluster)).To(Succeed()) 674 g.Expect(cluster.Spec.NetworkSpec).To(Equal(tt.afterCluster.Spec.NetworkSpec)) 675 }) 676 } 677 } 678 679 func TestAWSCluster_ValidateAllowedCIDRBlocks(t *testing.T) { 680 tests := []struct { 681 name string 682 awsc *AWSCluster 683 wantErr bool 684 }{ 685 { 686 name: "allow valid CIDRs", 687 awsc: &AWSCluster{ 688 Spec: AWSClusterSpec{ 689 Bastion: Bastion{ 690 AllowedCIDRBlocks: []string{ 691 "192.168.0.0/16", 692 "192.168.0.1/32", 693 }, 694 }, 695 }, 696 }, 697 wantErr: false, 698 }, 699 { 700 name: "disableIngressRules allowed with empty CIDR block", 701 awsc: &AWSCluster{ 702 Spec: AWSClusterSpec{ 703 Bastion: Bastion{ 704 AllowedCIDRBlocks: []string{}, 705 DisableIngressRules: true, 706 }, 707 }, 708 }, 709 wantErr: false, 710 }, 711 { 712 name: "disableIngressRules not allowed with CIDR blocks", 713 awsc: &AWSCluster{ 714 Spec: AWSClusterSpec{ 715 Bastion: Bastion{ 716 AllowedCIDRBlocks: []string{ 717 "192.168.0.0/16", 718 "192.168.0.1/32", 719 }, 720 DisableIngressRules: true, 721 }, 722 }, 723 }, 724 wantErr: true, 725 }, 726 { 727 name: "invalid CIDR block with invalid network", 728 awsc: &AWSCluster{ 729 Spec: AWSClusterSpec{ 730 Bastion: Bastion{ 731 AllowedCIDRBlocks: []string{ 732 "100.200.300.400/99", 733 }, 734 }, 735 }, 736 }, 737 wantErr: true, 738 }, 739 { 740 name: "invalid CIDR block with garbage string", 741 awsc: &AWSCluster{ 742 Spec: AWSClusterSpec{ 743 Bastion: Bastion{ 744 AllowedCIDRBlocks: []string{ 745 "abcdefg", 746 }, 747 }, 748 }, 749 }, 750 wantErr: true, 751 }, 752 } 753 for _, tt := range tests { 754 t.Run(tt.name, func(t *testing.T) { 755 ctx := context.TODO() 756 cluster := tt.awsc.DeepCopy() 757 cluster.ObjectMeta = metav1.ObjectMeta{ 758 GenerateName: "cluster-", 759 Namespace: "default", 760 } 761 if err := testEnv.Create(ctx, cluster); (err != nil) != tt.wantErr { 762 t.Errorf("ValidateAllowedCIDRBlocks() error = %v, wantErr %v", err, tt.wantErr) 763 } 764 }) 765 } 766 } 767 768 func TestAWSCluster_DefaultAllowedCIDRBlocks(t *testing.T) { 769 g := NewWithT(t) 770 tests := []struct { 771 name string 772 beforeCluster *AWSCluster 773 afterCluster *AWSCluster 774 }{ 775 { 776 name: "empty AllowedCIDRBlocks is defaulted to allow open ingress to bastion host", 777 beforeCluster: &AWSCluster{ 778 Spec: AWSClusterSpec{}, 779 }, 780 afterCluster: &AWSCluster{ 781 Spec: AWSClusterSpec{ 782 Bastion: Bastion{ 783 AllowedCIDRBlocks: []string{ 784 "0.0.0.0/0", 785 }, 786 }, 787 }, 788 }, 789 }, 790 { 791 name: "AllowedCIDRBlocks change not allowed if DisableIngressRules is true", 792 beforeCluster: &AWSCluster{ 793 Spec: AWSClusterSpec{ 794 Bastion: Bastion{ 795 AllowedCIDRBlocks: []string{"0.0.0.0/0"}, 796 DisableIngressRules: true, 797 Enabled: true, 798 }, 799 }, 800 }, 801 afterCluster: &AWSCluster{ 802 Spec: AWSClusterSpec{ 803 Bastion: Bastion{ 804 DisableIngressRules: true, 805 Enabled: true, 806 }, 807 }, 808 }, 809 }, 810 } 811 812 for _, tt := range tests { 813 t.Run(tt.name, func(t *testing.T) { 814 ctx := context.TODO() 815 cluster := tt.beforeCluster.DeepCopy() 816 cluster.ObjectMeta = metav1.ObjectMeta{ 817 GenerateName: "cluster-", 818 Namespace: "default", 819 } 820 err := testEnv.Create(ctx, cluster) 821 if err != nil { 822 g.Expect(err).To(HaveOccurred()) 823 } else { 824 g.Expect(cluster.Spec.Bastion).To(Equal(tt.afterCluster.Spec.Bastion)) 825 } 826 }) 827 } 828 }