sigs.k8s.io/cluster-api-provider-aws@v1.5.5/pkg/cloud/services/ec2/launchtemplate_test.go (about)

     1  /*
     2  Copyright 2020 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 ec2
    18  
    19  import (
    20  	"encoding/base64"
    21  	"testing"
    22  
    23  	"github.com/aws/aws-sdk-go/aws"
    24  	"github.com/aws/aws-sdk-go/aws/awserr"
    25  	"github.com/aws/aws-sdk-go/service/ec2"
    26  	"github.com/aws/aws-sdk-go/service/ssm"
    27  	"github.com/golang/mock/gomock"
    28  	"github.com/google/go-cmp/cmp"
    29  	. "github.com/onsi/gomega"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/utils/pointer"
    32  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    33  
    34  	infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1"
    35  	expinfrav1 "sigs.k8s.io/cluster-api-provider-aws/exp/api/v1beta1"
    36  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/awserrors"
    37  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/scope"
    38  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/services/ssm/mock_ssmiface"
    39  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/services/userdata"
    40  	"sigs.k8s.io/cluster-api-provider-aws/test/mocks"
    41  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    42  )
    43  
    44  const (
    45  	testUserData = `## template: jinja
    46  #cloud-config
    47  
    48  write_files:
    49  -   path: /tmp/kubeadm-join-config.yaml
    50  	owner: root:root
    51  	permissions: '0640'
    52  	content: |
    53  	  ---
    54  	  apiVersion: kubeadm.k8s.io/v1beta2
    55  	  discovery:
    56  		bootstrapToken:
    57  		  apiServerEndpoint: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    58  		  caCertHashes:
    59  		  - sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    60  		  token: xxxxxx.xxxxxxxxxxxxxxxx
    61  		  unsafeSkipCAVerification: false
    62  	  kind: JoinConfiguration
    63  	  nodeRegistration:
    64  		kubeletExtraArgs:
    65  		  cloud-provider: aws
    66  		name: '{{ ds.meta_data.local_hostname }}'
    67  
    68  runcmd:
    69    - kubeadm join --config /tmp/kubeadm-join-config.yaml
    70  users:
    71    - name: xxxxxxxx
    72  	sudo: ALL=(ALL) NOPASSWD:ALL
    73  	ssh_authorized_keys:
    74  	  - ssh-rsa xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx user@example.com
    75  `
    76  )
    77  
    78  var (
    79  	testUserDataHash = userdata.ComputeHash([]byte(testUserData))
    80  )
    81  
    82  func TestGetLaunchTemplate(t *testing.T) {
    83  	mockCtrl := gomock.NewController(t)
    84  	defer mockCtrl.Finish()
    85  
    86  	testCases := []struct {
    87  		name               string
    88  		launchTemplateName string
    89  		expect             func(m *mocks.MockEC2APIMockRecorder)
    90  		check              func(g *WithT, launchTemplate *expinfrav1.AWSLaunchTemplate, userDataHash string, err error)
    91  	}{
    92  		{
    93  			name: "Should not return launch template if empty launch template name passed",
    94  			check: func(g *WithT, launchTemplate *expinfrav1.AWSLaunchTemplate, userDataHash string, err error) {
    95  				g.Expect(err).NotTo(HaveOccurred())
    96  				g.Expect(userDataHash).Should(BeEmpty())
    97  				g.Expect(launchTemplate).Should(BeNil())
    98  			},
    99  		},
   100  		{
   101  			name:               "Should not return error if no launch template exist with given name",
   102  			launchTemplateName: "foo",
   103  			expect: func(m *mocks.MockEC2APIMockRecorder) {
   104  				m.DescribeLaunchTemplateVersions(gomock.Eq(&ec2.DescribeLaunchTemplateVersionsInput{
   105  					LaunchTemplateName: aws.String("foo"),
   106  					Versions:           []*string{aws.String("$Latest")},
   107  				})).
   108  					Return(nil, awserr.New(
   109  						awserrors.LaunchTemplateNameNotFound,
   110  						"The specified launch template, with template name foo, does not exist.",
   111  						nil,
   112  					))
   113  			},
   114  			check: func(g *WithT, launchTemplate *expinfrav1.AWSLaunchTemplate, userDataHash string, err error) {
   115  				g.Expect(err).NotTo(HaveOccurred())
   116  				g.Expect(userDataHash).Should(BeEmpty())
   117  				g.Expect(launchTemplate).Should(BeNil())
   118  			},
   119  		},
   120  		{
   121  			name:               "Should return error if AWS failed during launch template fetching",
   122  			launchTemplateName: "foo",
   123  			expect: func(m *mocks.MockEC2APIMockRecorder) {
   124  				m.DescribeLaunchTemplateVersions(gomock.Eq(&ec2.DescribeLaunchTemplateVersionsInput{
   125  					LaunchTemplateName: aws.String("foo"),
   126  					Versions:           []*string{aws.String("$Latest")},
   127  				})).Return(nil, awserrors.NewFailedDependency("dependency failure"))
   128  			},
   129  			check: func(g *WithT, launchTemplate *expinfrav1.AWSLaunchTemplate, userDataHash string, err error) {
   130  				g.Expect(err).To(HaveOccurred())
   131  				g.Expect(userDataHash).Should(BeEmpty())
   132  				g.Expect(launchTemplate).Should(BeNil())
   133  			},
   134  		},
   135  		{
   136  			name:               "Should not return with error if no launch template versions received from AWS",
   137  			launchTemplateName: "foo",
   138  			expect: func(m *mocks.MockEC2APIMockRecorder) {
   139  				m.DescribeLaunchTemplateVersions(gomock.Eq(&ec2.DescribeLaunchTemplateVersionsInput{
   140  					LaunchTemplateName: aws.String("foo"),
   141  					Versions:           []*string{aws.String("$Latest")},
   142  				})).Return(nil, nil)
   143  			},
   144  			check: func(g *WithT, launchTemplate *expinfrav1.AWSLaunchTemplate, userDataHash string, err error) {
   145  				g.Expect(err).NotTo(HaveOccurred())
   146  				g.Expect(userDataHash).Should(BeEmpty())
   147  				g.Expect(launchTemplate).Should(BeNil())
   148  			},
   149  		},
   150  		{
   151  			name:               "Should successfully return launch template if exist with given name",
   152  			launchTemplateName: "foo",
   153  			expect: func(m *mocks.MockEC2APIMockRecorder) {
   154  				m.DescribeLaunchTemplateVersions(gomock.Eq(&ec2.DescribeLaunchTemplateVersionsInput{
   155  					LaunchTemplateName: aws.String("foo"),
   156  					Versions:           []*string{aws.String("$Latest")},
   157  				})).Return(&ec2.DescribeLaunchTemplateVersionsOutput{
   158  					LaunchTemplateVersions: []*ec2.LaunchTemplateVersion{
   159  						{
   160  							LaunchTemplateId:   aws.String("lt-12345"),
   161  							LaunchTemplateName: aws.String("foo"),
   162  							LaunchTemplateData: &ec2.ResponseLaunchTemplateData{
   163  								SecurityGroupIds: []*string{aws.String("sg-id")},
   164  								ImageId:          aws.String("foo-image"),
   165  								IamInstanceProfile: &ec2.LaunchTemplateIamInstanceProfileSpecification{
   166  									Arn: aws.String("instance-profile/foo-profile"),
   167  								},
   168  								KeyName: aws.String("foo-keyname"),
   169  								BlockDeviceMappings: []*ec2.LaunchTemplateBlockDeviceMapping{
   170  									{
   171  										DeviceName: aws.String("foo-device"),
   172  										Ebs: &ec2.LaunchTemplateEbsBlockDevice{
   173  											Encrypted:  aws.Bool(true),
   174  											VolumeSize: aws.Int64(16),
   175  											VolumeType: aws.String("cool"),
   176  										},
   177  									},
   178  								},
   179  								NetworkInterfaces: []*ec2.LaunchTemplateInstanceNetworkInterfaceSpecification{
   180  									{
   181  										DeviceIndex: aws.Int64(1),
   182  										Groups:      []*string{aws.String("foo-group")},
   183  									},
   184  								},
   185  								UserData: aws.String(base64.StdEncoding.EncodeToString([]byte(testUserData))),
   186  							},
   187  							VersionNumber: aws.Int64(1),
   188  						},
   189  					},
   190  				}, nil)
   191  			},
   192  			check: func(g *WithT, launchTemplate *expinfrav1.AWSLaunchTemplate, userDataHash string, err error) {
   193  				wantLT := &expinfrav1.AWSLaunchTemplate{
   194  					Name: "foo",
   195  					AMI: infrav1.AMIReference{
   196  						ID: aws.String("foo-image"),
   197  					},
   198  					IamInstanceProfile:       "foo-profile",
   199  					SSHKeyName:               aws.String("foo-keyname"),
   200  					VersionNumber:            aws.Int64(1),
   201  					AdditionalSecurityGroups: []infrav1.AWSResourceReference{{ID: aws.String("sg-id")}},
   202  				}
   203  
   204  				g.Expect(err).NotTo(HaveOccurred())
   205  				g.Expect(userDataHash).Should(Equal(testUserDataHash))
   206  				g.Expect(launchTemplate).Should(Equal(wantLT))
   207  			},
   208  		},
   209  		{
   210  			name:               "Should return computed userData if AWS returns empty userData",
   211  			launchTemplateName: "foo",
   212  			expect: func(m *mocks.MockEC2APIMockRecorder) {
   213  				m.DescribeLaunchTemplateVersions(gomock.Eq(&ec2.DescribeLaunchTemplateVersionsInput{
   214  					LaunchTemplateName: aws.String("foo"),
   215  					Versions:           []*string{aws.String("$Latest")},
   216  				})).Return(&ec2.DescribeLaunchTemplateVersionsOutput{
   217  					LaunchTemplateVersions: []*ec2.LaunchTemplateVersion{
   218  						{
   219  							LaunchTemplateId:   aws.String("lt-12345"),
   220  							LaunchTemplateName: aws.String("foo"),
   221  							LaunchTemplateData: &ec2.ResponseLaunchTemplateData{
   222  								SecurityGroupIds: []*string{aws.String("sg-id")},
   223  								ImageId:          aws.String("foo-image"),
   224  								IamInstanceProfile: &ec2.LaunchTemplateIamInstanceProfileSpecification{
   225  									Arn: aws.String("instance-profile/foo-profile"),
   226  								},
   227  								KeyName: aws.String("foo-keyname"),
   228  								BlockDeviceMappings: []*ec2.LaunchTemplateBlockDeviceMapping{
   229  									{
   230  										DeviceName: aws.String("foo-device"),
   231  										Ebs: &ec2.LaunchTemplateEbsBlockDevice{
   232  											Encrypted:  aws.Bool(true),
   233  											VolumeSize: aws.Int64(16),
   234  											VolumeType: aws.String("cool"),
   235  										},
   236  									},
   237  								},
   238  								NetworkInterfaces: []*ec2.LaunchTemplateInstanceNetworkInterfaceSpecification{
   239  									{
   240  										DeviceIndex: aws.Int64(1),
   241  										Groups:      []*string{aws.String("foo-group")},
   242  									},
   243  								},
   244  							},
   245  							VersionNumber: aws.Int64(1),
   246  						},
   247  					},
   248  				}, nil)
   249  			},
   250  			check: func(g *WithT, launchTemplate *expinfrav1.AWSLaunchTemplate, userDataHash string, err error) {
   251  				wantLT := &expinfrav1.AWSLaunchTemplate{
   252  					Name: "foo",
   253  					AMI: infrav1.AMIReference{
   254  						ID: aws.String("foo-image"),
   255  					},
   256  					IamInstanceProfile:       "foo-profile",
   257  					SSHKeyName:               aws.String("foo-keyname"),
   258  					VersionNumber:            aws.Int64(1),
   259  					AdditionalSecurityGroups: []infrav1.AWSResourceReference{{ID: aws.String("sg-id")}},
   260  				}
   261  
   262  				g.Expect(err).NotTo(HaveOccurred())
   263  				g.Expect(userDataHash).Should(Equal(userdata.ComputeHash(nil)))
   264  				g.Expect(launchTemplate).Should(Equal(wantLT))
   265  			},
   266  		},
   267  	}
   268  	for _, tc := range testCases {
   269  		t.Run(tc.name, func(t *testing.T) {
   270  			g := NewWithT(t)
   271  
   272  			scheme, err := setupScheme()
   273  			g.Expect(err).NotTo(HaveOccurred())
   274  			client := fake.NewClientBuilder().WithScheme(scheme).Build()
   275  
   276  			cs, err := setupClusterScope(client)
   277  			g.Expect(err).NotTo(HaveOccurred())
   278  			mockEC2Client := mocks.NewMockEC2API(mockCtrl)
   279  
   280  			s := NewService(cs)
   281  			s.EC2Client = mockEC2Client
   282  
   283  			if tc.expect != nil {
   284  				tc.expect(mockEC2Client.EXPECT())
   285  			}
   286  
   287  			launchTemplate, userData, err := s.GetLaunchTemplate(tc.launchTemplateName)
   288  			tc.check(g, launchTemplate, userData, err)
   289  		})
   290  	}
   291  }
   292  
   293  func TestService_SDKToLaunchTemplate(t *testing.T) {
   294  	tests := []struct {
   295  		name     string
   296  		input    *ec2.LaunchTemplateVersion
   297  		wantLT   *expinfrav1.AWSLaunchTemplate
   298  		wantHash string
   299  		wantErr  bool
   300  	}{
   301  		{
   302  			name: "lots of input",
   303  			input: &ec2.LaunchTemplateVersion{
   304  				LaunchTemplateId:   aws.String("lt-12345"),
   305  				LaunchTemplateName: aws.String("foo"),
   306  				LaunchTemplateData: &ec2.ResponseLaunchTemplateData{
   307  					ImageId: aws.String("foo-image"),
   308  					IamInstanceProfile: &ec2.LaunchTemplateIamInstanceProfileSpecification{
   309  						Arn: aws.String("instance-profile/foo-profile"),
   310  					},
   311  					KeyName: aws.String("foo-keyname"),
   312  					BlockDeviceMappings: []*ec2.LaunchTemplateBlockDeviceMapping{
   313  						{
   314  							DeviceName: aws.String("foo-device"),
   315  							Ebs: &ec2.LaunchTemplateEbsBlockDevice{
   316  								Encrypted:  aws.Bool(true),
   317  								VolumeSize: aws.Int64(16),
   318  								VolumeType: aws.String("cool"),
   319  							},
   320  						},
   321  					},
   322  					NetworkInterfaces: []*ec2.LaunchTemplateInstanceNetworkInterfaceSpecification{
   323  						{
   324  							DeviceIndex: aws.Int64(1),
   325  							Groups:      []*string{aws.String("foo-group")},
   326  						},
   327  					},
   328  					UserData: aws.String(base64.StdEncoding.EncodeToString([]byte(testUserData))),
   329  				},
   330  				VersionNumber: aws.Int64(1),
   331  			},
   332  			wantLT: &expinfrav1.AWSLaunchTemplate{
   333  				Name: "foo",
   334  				AMI: infrav1.AMIReference{
   335  					ID: aws.String("foo-image"),
   336  				},
   337  				IamInstanceProfile: "foo-profile",
   338  				SSHKeyName:         aws.String("foo-keyname"),
   339  				VersionNumber:      aws.Int64(1),
   340  			},
   341  			wantHash: testUserDataHash,
   342  		},
   343  	}
   344  	for _, tt := range tests {
   345  		t.Run(tt.name, func(t *testing.T) {
   346  			s := &Service{}
   347  			gotLT, gotHash, err := s.SDKToLaunchTemplate(tt.input)
   348  			if (err != nil) != tt.wantErr {
   349  				t.Fatalf("error mismatch: got %v, wantErr %v", err, tt.wantErr)
   350  			}
   351  			if !cmp.Equal(gotLT, tt.wantLT) {
   352  				t.Fatalf("launchTemplate mismatch: got %v, want %v", gotLT, tt.wantLT)
   353  			}
   354  			if !cmp.Equal(gotHash, tt.wantHash) {
   355  				t.Fatalf("userDataHash mismatch: got %v, want %v", gotHash, tt.wantHash)
   356  			}
   357  		})
   358  	}
   359  }
   360  
   361  func TestService_LaunchTemplateNeedsUpdate(t *testing.T) {
   362  	mockCtrl := gomock.NewController(t)
   363  	defer mockCtrl.Finish()
   364  
   365  	tests := []struct {
   366  		name     string
   367  		incoming *expinfrav1.AWSLaunchTemplate
   368  		existing *expinfrav1.AWSLaunchTemplate
   369  		expect   func(m *mocks.MockEC2APIMockRecorder)
   370  		want     bool
   371  		wantErr  bool
   372  	}{
   373  		{
   374  			name: "the same security groups",
   375  			incoming: &expinfrav1.AWSLaunchTemplate{
   376  				AdditionalSecurityGroups: []infrav1.AWSResourceReference{
   377  					{ID: aws.String("sg-999")},
   378  				},
   379  			},
   380  			existing: &expinfrav1.AWSLaunchTemplate{
   381  				AdditionalSecurityGroups: []infrav1.AWSResourceReference{
   382  					{ID: aws.String("sg-111")},
   383  					{ID: aws.String("sg-222")},
   384  					{ID: aws.String("sg-999")},
   385  				},
   386  			},
   387  			want:    false,
   388  			wantErr: false,
   389  		},
   390  		{
   391  			name: "core security group removed externally",
   392  			incoming: &expinfrav1.AWSLaunchTemplate{
   393  				AdditionalSecurityGroups: []infrav1.AWSResourceReference{
   394  					{ID: aws.String("sg-999")},
   395  				},
   396  			},
   397  			existing: &expinfrav1.AWSLaunchTemplate{
   398  				AdditionalSecurityGroups: []infrav1.AWSResourceReference{
   399  					{ID: aws.String("sg-222")},
   400  					{ID: aws.String("sg-999")},
   401  				},
   402  			},
   403  			want:    true,
   404  			wantErr: false,
   405  		},
   406  		{
   407  			name: "new additional security group",
   408  			incoming: &expinfrav1.AWSLaunchTemplate{
   409  				AdditionalSecurityGroups: []infrav1.AWSResourceReference{
   410  					{ID: aws.String("sg-999")},
   411  					{ID: aws.String("sg-000")},
   412  				},
   413  			},
   414  			existing: &expinfrav1.AWSLaunchTemplate{
   415  				AdditionalSecurityGroups: []infrav1.AWSResourceReference{
   416  					{ID: aws.String("sg-111")},
   417  					{ID: aws.String("sg-222")},
   418  					{ID: aws.String("sg-999")},
   419  				},
   420  			},
   421  			want:    true,
   422  			wantErr: false,
   423  		},
   424  		{
   425  			name: "Should return true if incoming IamInstanceProfile is not same as existing IamInstanceProfile",
   426  			incoming: &expinfrav1.AWSLaunchTemplate{
   427  				IamInstanceProfile: DefaultAmiNameFormat,
   428  			},
   429  			existing: &expinfrav1.AWSLaunchTemplate{
   430  				IamInstanceProfile: "some-other-profile",
   431  			},
   432  			want: true,
   433  		},
   434  		{
   435  			name: "Should return true if incoming InstanceType is not same as existing InstanceType",
   436  			incoming: &expinfrav1.AWSLaunchTemplate{
   437  				InstanceType: "t3.micro",
   438  			},
   439  			existing: &expinfrav1.AWSLaunchTemplate{
   440  				InstanceType: "t3.large",
   441  			},
   442  			want: true,
   443  		},
   444  		{
   445  			name: "new additional security group with filters",
   446  			incoming: &expinfrav1.AWSLaunchTemplate{
   447  				AdditionalSecurityGroups: []infrav1.AWSResourceReference{
   448  					{Filters: []infrav1.Filter{{Name: "sg-1", Values: []string{"test-1"}}}},
   449  				},
   450  			},
   451  			existing: &expinfrav1.AWSLaunchTemplate{
   452  				AdditionalSecurityGroups: []infrav1.AWSResourceReference{
   453  					{Filters: []infrav1.Filter{{Name: "sg-2", Values: []string{"test-2"}}}},
   454  				},
   455  			},
   456  			expect: func(m *mocks.MockEC2APIMockRecorder) {
   457  				m.DescribeSecurityGroups(gomock.Eq(&ec2.DescribeSecurityGroupsInput{Filters: []*ec2.Filter{{Name: aws.String("sg-1"), Values: aws.StringSlice([]string{"test-1"})}}})).
   458  					Return(&ec2.DescribeSecurityGroupsOutput{SecurityGroups: []*ec2.SecurityGroup{{GroupId: aws.String("sg-1")}}}, nil)
   459  				m.DescribeSecurityGroups(gomock.Eq(&ec2.DescribeSecurityGroupsInput{Filters: []*ec2.Filter{{Name: aws.String("sg-2"), Values: aws.StringSlice([]string{"test-2"})}}})).
   460  					Return(&ec2.DescribeSecurityGroupsOutput{SecurityGroups: []*ec2.SecurityGroup{{GroupId: aws.String("sg-2")}}}, nil)
   461  			},
   462  			want:    true,
   463  			wantErr: false,
   464  		},
   465  	}
   466  	for _, tt := range tests {
   467  		t.Run(tt.name, func(t *testing.T) {
   468  			g := NewWithT(t)
   469  			ac := &infrav1.AWSCluster{
   470  				ObjectMeta: metav1.ObjectMeta{Name: "test"},
   471  				Status: infrav1.AWSClusterStatus{
   472  					Network: infrav1.NetworkStatus{
   473  						SecurityGroups: map[infrav1.SecurityGroupRole]infrav1.SecurityGroup{
   474  							infrav1.SecurityGroupNode: {
   475  								ID: "sg-111",
   476  							},
   477  							infrav1.SecurityGroupLB: {
   478  								ID: "sg-222",
   479  							},
   480  						},
   481  					},
   482  				},
   483  			}
   484  			s := &Service{
   485  				scope: &scope.ClusterScope{
   486  					AWSCluster: ac,
   487  				},
   488  			}
   489  			machinePoolScope := &scope.MachinePoolScope{
   490  				InfraCluster: &scope.ClusterScope{
   491  					AWSCluster: ac,
   492  				},
   493  			}
   494  			mockEC2Client := mocks.NewMockEC2API(mockCtrl)
   495  			s.EC2Client = mockEC2Client
   496  
   497  			if tt.expect != nil {
   498  				tt.expect(mockEC2Client.EXPECT())
   499  			}
   500  
   501  			got, err := s.LaunchTemplateNeedsUpdate(machinePoolScope, tt.incoming, tt.existing)
   502  			if tt.wantErr {
   503  				g.Expect(err).To(HaveOccurred())
   504  				return
   505  			}
   506  			g.Expect(err).NotTo(HaveOccurred())
   507  			g.Expect(got).Should(Equal(tt.want))
   508  		})
   509  	}
   510  }
   511  
   512  func TestGetLaunchTemplateID(t *testing.T) {
   513  	mockCtrl := gomock.NewController(t)
   514  	defer mockCtrl.Finish()
   515  
   516  	testCases := []struct {
   517  		name               string
   518  		launchTemplateName string
   519  		expect             func(m *mocks.MockEC2APIMockRecorder)
   520  		check              func(g *WithT, launchTemplateID string, err error)
   521  	}{
   522  		{
   523  			name:   "Should return with no error if empty launch template name passed",
   524  			expect: func(m *mocks.MockEC2APIMockRecorder) {},
   525  			check: func(g *WithT, launchTemplateID string, err error) {
   526  				g.Expect(err).NotTo(HaveOccurred())
   527  				g.Expect(launchTemplateID).Should(BeEmpty())
   528  			},
   529  		},
   530  		{
   531  			name:               "Should not return error if launch template does not exist",
   532  			launchTemplateName: "foo",
   533  			expect: func(m *mocks.MockEC2APIMockRecorder) {
   534  				m.DescribeLaunchTemplateVersions(gomock.Eq(&ec2.DescribeLaunchTemplateVersionsInput{
   535  					LaunchTemplateName: aws.String("foo"),
   536  					Versions:           []*string{aws.String("$Latest")},
   537  				})).Return(nil, awserr.New(
   538  					awserrors.LaunchTemplateNameNotFound,
   539  					"The specified launch template, with template name foo, does not exist.",
   540  					nil,
   541  				))
   542  			},
   543  			check: func(g *WithT, launchTemplateID string, err error) {
   544  				g.Expect(err).NotTo(HaveOccurred())
   545  				g.Expect(launchTemplateID).Should(BeEmpty())
   546  			},
   547  		},
   548  		{
   549  			name:               "Should return with error if AWS failed to fetch launch template",
   550  			launchTemplateName: "foo",
   551  			expect: func(m *mocks.MockEC2APIMockRecorder) {
   552  				m.DescribeLaunchTemplateVersions(gomock.Eq(&ec2.DescribeLaunchTemplateVersionsInput{
   553  					LaunchTemplateName: aws.String("foo"),
   554  					Versions:           []*string{aws.String("$Latest")},
   555  				})).Return(nil, awserrors.NewFailedDependency("Dependency issue from AWS"))
   556  			},
   557  			check: func(g *WithT, launchTemplateID string, err error) {
   558  				g.Expect(err).To(HaveOccurred())
   559  				g.Expect(launchTemplateID).Should(BeEmpty())
   560  			},
   561  		},
   562  		{
   563  			name:               "Should not return error if AWS returns no launch template versions info in output",
   564  			launchTemplateName: "foo",
   565  			expect: func(m *mocks.MockEC2APIMockRecorder) {
   566  				m.DescribeLaunchTemplateVersions(gomock.Eq(&ec2.DescribeLaunchTemplateVersionsInput{
   567  					LaunchTemplateName: aws.String("foo"),
   568  					Versions:           []*string{aws.String("$Latest")},
   569  				})).Return(nil, nil)
   570  			},
   571  			check: func(g *WithT, launchTemplateID string, err error) {
   572  				g.Expect(err).NotTo(HaveOccurred())
   573  				g.Expect(launchTemplateID).Should(BeEmpty())
   574  			},
   575  		},
   576  		{
   577  			name:               "Should successfully return launch template ID for given name if exists",
   578  			launchTemplateName: "foo",
   579  			expect: func(m *mocks.MockEC2APIMockRecorder) {
   580  				m.DescribeLaunchTemplateVersions(gomock.Eq(&ec2.DescribeLaunchTemplateVersionsInput{
   581  					LaunchTemplateName: aws.String("foo"),
   582  					Versions:           []*string{aws.String("$Latest")},
   583  				})).Return(&ec2.DescribeLaunchTemplateVersionsOutput{
   584  					LaunchTemplateVersions: []*ec2.LaunchTemplateVersion{
   585  						{
   586  							LaunchTemplateId:   aws.String("lt-12345"),
   587  							LaunchTemplateName: aws.String("foo"),
   588  							LaunchTemplateData: &ec2.ResponseLaunchTemplateData{
   589  								ImageId: aws.String("foo-image"),
   590  								IamInstanceProfile: &ec2.LaunchTemplateIamInstanceProfileSpecification{
   591  									Arn: aws.String("instance-profile/foo-profile"),
   592  								},
   593  								KeyName: aws.String("foo-keyname"),
   594  								BlockDeviceMappings: []*ec2.LaunchTemplateBlockDeviceMapping{
   595  									{
   596  										DeviceName: aws.String("foo-device"),
   597  										Ebs: &ec2.LaunchTemplateEbsBlockDevice{
   598  											Encrypted:  aws.Bool(true),
   599  											VolumeSize: aws.Int64(16),
   600  											VolumeType: aws.String("cool"),
   601  										},
   602  									},
   603  								},
   604  								NetworkInterfaces: []*ec2.LaunchTemplateInstanceNetworkInterfaceSpecification{
   605  									{
   606  										DeviceIndex: aws.Int64(1),
   607  										Groups:      []*string{aws.String("foo-group")},
   608  									},
   609  								},
   610  								UserData: aws.String(base64.StdEncoding.EncodeToString([]byte(testUserData))),
   611  							},
   612  							VersionNumber: aws.Int64(1),
   613  						},
   614  					},
   615  				}, nil)
   616  			},
   617  			check: func(g *WithT, launchTemplateID string, err error) {
   618  				g.Expect(err).NotTo(HaveOccurred())
   619  				g.Expect(launchTemplateID).Should(Equal("lt-12345"))
   620  			},
   621  		},
   622  	}
   623  
   624  	for _, tc := range testCases {
   625  		t.Run(tc.name, func(t *testing.T) {
   626  			g := NewWithT(t)
   627  
   628  			scheme, err := setupScheme()
   629  			g.Expect(err).NotTo(HaveOccurred())
   630  			client := fake.NewClientBuilder().WithScheme(scheme).Build()
   631  
   632  			cs, err := setupClusterScope(client)
   633  			g.Expect(err).NotTo(HaveOccurred())
   634  			mockEC2Client := mocks.NewMockEC2API(mockCtrl)
   635  
   636  			s := NewService(cs)
   637  			s.EC2Client = mockEC2Client
   638  
   639  			if tc.expect != nil {
   640  				tc.expect(mockEC2Client.EXPECT())
   641  			}
   642  			launchTemplate, err := s.GetLaunchTemplateID(tc.launchTemplateName)
   643  			tc.check(g, launchTemplate, err)
   644  		})
   645  	}
   646  }
   647  
   648  func TestDeleteLaunchTemplate(t *testing.T) {
   649  	mockCtrl := gomock.NewController(t)
   650  	defer mockCtrl.Finish()
   651  
   652  	testCases := []struct {
   653  		name      string
   654  		versionID string
   655  		expect    func(m *mocks.MockEC2APIMockRecorder)
   656  		wantErr   bool
   657  	}{
   658  		{
   659  			name:      "Should not return error if successfully deletes given launch template ID",
   660  			versionID: "1",
   661  			expect: func(m *mocks.MockEC2APIMockRecorder) {
   662  				m.DeleteLaunchTemplate(gomock.Eq(&ec2.DeleteLaunchTemplateInput{
   663  					LaunchTemplateId: aws.String("1"),
   664  				})).Return(&ec2.DeleteLaunchTemplateOutput{}, nil)
   665  			},
   666  		},
   667  		{
   668  			name:      "Should return error if failed to delete given launch template ID",
   669  			versionID: "1",
   670  			expect: func(m *mocks.MockEC2APIMockRecorder) {
   671  				m.DeleteLaunchTemplate(gomock.Eq(&ec2.DeleteLaunchTemplateInput{
   672  					LaunchTemplateId: aws.String("1"),
   673  				})).Return(nil, awserrors.NewFailedDependency("dependency failure"))
   674  			},
   675  			wantErr: true,
   676  		},
   677  	}
   678  	for _, tc := range testCases {
   679  		t.Run(tc.name, func(t *testing.T) {
   680  			g := NewWithT(t)
   681  
   682  			scheme, err := setupScheme()
   683  			g.Expect(err).NotTo(HaveOccurred())
   684  			client := fake.NewClientBuilder().WithScheme(scheme).Build()
   685  
   686  			cs, err := setupClusterScope(client)
   687  			g.Expect(err).NotTo(HaveOccurred())
   688  			mockEC2Client := mocks.NewMockEC2API(mockCtrl)
   689  
   690  			s := NewService(cs)
   691  			s.EC2Client = mockEC2Client
   692  			tc.expect(mockEC2Client.EXPECT())
   693  
   694  			err = s.DeleteLaunchTemplate(tc.versionID)
   695  			if tc.wantErr {
   696  				g.Expect(err).To(HaveOccurred())
   697  				return
   698  			}
   699  			g.Expect(err).NotTo(HaveOccurred())
   700  		})
   701  	}
   702  }
   703  
   704  func TestCreateLaunchTemplate(t *testing.T) {
   705  	mockCtrl := gomock.NewController(t)
   706  	defer mockCtrl.Finish()
   707  
   708  	var formatTagsInput = func(arg *ec2.CreateLaunchTemplateInput) {
   709  		sortTags(arg.TagSpecifications[0].Tags)
   710  
   711  		for index := range arg.LaunchTemplateData.TagSpecifications {
   712  			sortTags(arg.LaunchTemplateData.TagSpecifications[index].Tags)
   713  		}
   714  	}
   715  
   716  	var userData = []byte{1, 0, 0}
   717  	testCases := []struct {
   718  		name                 string
   719  		awsResourceReference []infrav1.AWSResourceReference
   720  		expect               func(g *WithT, m *mocks.MockEC2APIMockRecorder)
   721  		check                func(g *WithT, s string, e error)
   722  	}{
   723  		{
   724  			name:                 "Should not return error if successfully created launch template id",
   725  			awsResourceReference: []infrav1.AWSResourceReference{{ID: aws.String("1")}},
   726  			expect: func(g *WithT, m *mocks.MockEC2APIMockRecorder) {
   727  				sgMap := make(map[infrav1.SecurityGroupRole]infrav1.SecurityGroup)
   728  				sgMap[infrav1.SecurityGroupNode] = infrav1.SecurityGroup{ID: "1"}
   729  				sgMap[infrav1.SecurityGroupLB] = infrav1.SecurityGroup{ID: "2"}
   730  
   731  				var expectedInput = &ec2.CreateLaunchTemplateInput{
   732  					LaunchTemplateData: &ec2.RequestLaunchTemplateData{
   733  						InstanceType: aws.String("t3.large"),
   734  						IamInstanceProfile: &ec2.LaunchTemplateIamInstanceProfileSpecificationRequest{
   735  							Name: aws.String("instance-profile"),
   736  						},
   737  						KeyName:          aws.String("default"),
   738  						UserData:         pointer.StringPtr(base64.StdEncoding.EncodeToString(userData)),
   739  						SecurityGroupIds: aws.StringSlice([]string{"nodeSG", "lbSG", "1"}),
   740  						ImageId:          aws.String("imageID"),
   741  						TagSpecifications: []*ec2.LaunchTemplateTagSpecificationRequest{
   742  							{
   743  								ResourceType: aws.String(ec2.ResourceTypeInstance),
   744  								Tags:         defaultEC2Tags("aws-mp-name", "cluster-name"),
   745  							},
   746  							{
   747  								ResourceType: aws.String(ec2.ResourceTypeVolume),
   748  								Tags:         defaultEC2Tags("aws-mp-name", "cluster-name"),
   749  							},
   750  						},
   751  					},
   752  					LaunchTemplateName: aws.String("aws-mp-name"),
   753  					TagSpecifications: []*ec2.TagSpecification{
   754  						{
   755  							ResourceType: aws.String(ec2.ResourceTypeLaunchTemplate),
   756  							Tags:         defaultEC2Tags("aws-mp-name", "cluster-name"),
   757  						},
   758  					},
   759  				}
   760  				m.CreateLaunchTemplate(gomock.AssignableToTypeOf(expectedInput)).Return(&ec2.CreateLaunchTemplateOutput{
   761  					LaunchTemplate: &ec2.LaunchTemplate{
   762  						LaunchTemplateId: aws.String("launch-template-id"),
   763  					},
   764  				}, nil).Do(func(arg *ec2.CreateLaunchTemplateInput) {
   765  					// formatting added to match arrays during cmp.Equal
   766  					formatTagsInput(arg)
   767  					if !cmp.Equal(expectedInput, arg) {
   768  						t.Fatalf("mismatch in input expected: %+v, got: %+v", expectedInput, arg)
   769  					}
   770  				})
   771  			},
   772  			check: func(g *WithT, id string, err error) {
   773  				g.Expect(id).Should(Equal("launch-template-id"))
   774  				g.Expect(err).NotTo(HaveOccurred())
   775  			},
   776  		},
   777  		{
   778  			name:                 "Should successfully create launch template id with AdditionalSecurityGroups Filter",
   779  			awsResourceReference: []infrav1.AWSResourceReference{{Filters: []infrav1.Filter{{Name: "sg-1", Values: []string{"test"}}}}},
   780  			expect: func(g *WithT, m *mocks.MockEC2APIMockRecorder) {
   781  				sgMap := make(map[infrav1.SecurityGroupRole]infrav1.SecurityGroup)
   782  				sgMap[infrav1.SecurityGroupNode] = infrav1.SecurityGroup{ID: "1"}
   783  				sgMap[infrav1.SecurityGroupLB] = infrav1.SecurityGroup{ID: "2"}
   784  
   785  				var expectedInput = &ec2.CreateLaunchTemplateInput{
   786  					LaunchTemplateData: &ec2.RequestLaunchTemplateData{
   787  						InstanceType: aws.String("t3.large"),
   788  						IamInstanceProfile: &ec2.LaunchTemplateIamInstanceProfileSpecificationRequest{
   789  							Name: aws.String("instance-profile"),
   790  						},
   791  						KeyName:          aws.String("default"),
   792  						UserData:         pointer.StringPtr(base64.StdEncoding.EncodeToString(userData)),
   793  						SecurityGroupIds: aws.StringSlice([]string{"nodeSG", "lbSG", "sg-1"}),
   794  						ImageId:          aws.String("imageID"),
   795  						TagSpecifications: []*ec2.LaunchTemplateTagSpecificationRequest{
   796  							{
   797  								ResourceType: aws.String(ec2.ResourceTypeInstance),
   798  								Tags:         defaultEC2Tags("aws-mp-name", "cluster-name"),
   799  							},
   800  							{
   801  								ResourceType: aws.String(ec2.ResourceTypeVolume),
   802  								Tags:         defaultEC2Tags("aws-mp-name", "cluster-name"),
   803  							},
   804  						},
   805  					},
   806  					LaunchTemplateName: aws.String("aws-mp-name"),
   807  					TagSpecifications: []*ec2.TagSpecification{
   808  						{
   809  							ResourceType: aws.String(ec2.ResourceTypeLaunchTemplate),
   810  							Tags:         defaultEC2Tags("aws-mp-name", "cluster-name"),
   811  						},
   812  					},
   813  				}
   814  				m.CreateLaunchTemplate(gomock.AssignableToTypeOf(expectedInput)).Return(&ec2.CreateLaunchTemplateOutput{
   815  					LaunchTemplate: &ec2.LaunchTemplate{
   816  						LaunchTemplateId: aws.String("launch-template-id"),
   817  					},
   818  				}, nil).Do(func(arg *ec2.CreateLaunchTemplateInput) {
   819  					// formatting added to match arrays during reflect.DeepEqual
   820  					formatTagsInput(arg)
   821  					if !cmp.Equal(expectedInput, arg) {
   822  						t.Fatalf("mismatch in input expected: %+v, got: %+v", expectedInput, arg)
   823  					}
   824  				})
   825  				m.DescribeSecurityGroups(gomock.Eq(&ec2.DescribeSecurityGroupsInput{Filters: []*ec2.Filter{{Name: aws.String("sg-1"), Values: aws.StringSlice([]string{"test"})}}})).
   826  					Return(&ec2.DescribeSecurityGroupsOutput{SecurityGroups: []*ec2.SecurityGroup{{GroupId: aws.String("sg-1")}}}, nil)
   827  			},
   828  			check: func(g *WithT, id string, err error) {
   829  				g.Expect(id).Should(Equal("launch-template-id"))
   830  				g.Expect(err).NotTo(HaveOccurred())
   831  			},
   832  		},
   833  		{
   834  			name:                 "Should return with error if failed to create launch template id",
   835  			awsResourceReference: []infrav1.AWSResourceReference{{ID: aws.String("1")}},
   836  			expect: func(g *WithT, m *mocks.MockEC2APIMockRecorder) {
   837  				sgMap := make(map[infrav1.SecurityGroupRole]infrav1.SecurityGroup)
   838  				sgMap[infrav1.SecurityGroupNode] = infrav1.SecurityGroup{ID: "1"}
   839  				sgMap[infrav1.SecurityGroupLB] = infrav1.SecurityGroup{ID: "2"}
   840  
   841  				var expectedInput = &ec2.CreateLaunchTemplateInput{
   842  					LaunchTemplateData: &ec2.RequestLaunchTemplateData{
   843  						InstanceType: aws.String("t3.large"),
   844  						IamInstanceProfile: &ec2.LaunchTemplateIamInstanceProfileSpecificationRequest{
   845  							Name: aws.String("instance-profile"),
   846  						},
   847  						KeyName:          aws.String("default"),
   848  						UserData:         pointer.StringPtr(base64.StdEncoding.EncodeToString(userData)),
   849  						SecurityGroupIds: aws.StringSlice([]string{"nodeSG", "lbSG", "1"}),
   850  						ImageId:          aws.String("imageID"),
   851  						TagSpecifications: []*ec2.LaunchTemplateTagSpecificationRequest{
   852  							{
   853  								ResourceType: aws.String(ec2.ResourceTypeInstance),
   854  								Tags:         defaultEC2Tags("aws-mp-name", "cluster-name"),
   855  							},
   856  							{
   857  								ResourceType: aws.String(ec2.ResourceTypeVolume),
   858  								Tags:         defaultEC2Tags("aws-mp-name", "cluster-name"),
   859  							},
   860  						},
   861  					},
   862  					LaunchTemplateName: aws.String("aws-mp-name"),
   863  					TagSpecifications: []*ec2.TagSpecification{
   864  						{
   865  							ResourceType: aws.String(ec2.ResourceTypeLaunchTemplate),
   866  							Tags:         defaultEC2Tags("aws-mp-name", "cluster-name"),
   867  						},
   868  					},
   869  				}
   870  				m.CreateLaunchTemplate(gomock.AssignableToTypeOf(expectedInput)).Return(nil,
   871  					awserrors.NewFailedDependency("dependency failure")).Do(func(arg *ec2.CreateLaunchTemplateInput) {
   872  					// formatting added to match arrays during cmp.Equal
   873  					formatTagsInput(arg)
   874  					if !cmp.Equal(expectedInput, arg) {
   875  						t.Fatalf("mismatch in input expected: %+v, got: %+v", expectedInput, arg)
   876  					}
   877  				})
   878  			},
   879  			check: func(g *WithT, id string, err error) {
   880  				g.Expect(id).Should(BeEmpty())
   881  				g.Expect(err).To(HaveOccurred())
   882  			},
   883  		},
   884  	}
   885  	for _, tc := range testCases {
   886  		t.Run(tc.name, func(t *testing.T) {
   887  			g := NewWithT(t)
   888  
   889  			scheme, err := setupScheme()
   890  			g.Expect(err).NotTo(HaveOccurred())
   891  			client := fake.NewClientBuilder().WithScheme(scheme).Build()
   892  
   893  			cs, err := setupClusterScope(client)
   894  			g.Expect(err).NotTo(HaveOccurred())
   895  			mockEC2Client := mocks.NewMockEC2API(mockCtrl)
   896  
   897  			ms, err := setupMachinePoolScope(client, cs)
   898  			g.Expect(err).NotTo(HaveOccurred())
   899  
   900  			ms.AWSMachinePool.Spec.AWSLaunchTemplate.AdditionalSecurityGroups = tc.awsResourceReference
   901  
   902  			s := NewService(cs)
   903  			s.EC2Client = mockEC2Client
   904  
   905  			if tc.expect != nil {
   906  				tc.expect(g, mockEC2Client.EXPECT())
   907  			}
   908  
   909  			launchTemplate, err := s.CreateLaunchTemplate(ms, aws.String("imageID"), userData)
   910  			tc.check(g, launchTemplate, err)
   911  		})
   912  	}
   913  }
   914  
   915  func Test_LaunchTemplateDataCreation(t *testing.T) {
   916  	mockCtrl := gomock.NewController(t)
   917  	defer mockCtrl.Finish()
   918  	t.Run("Should return error if failed to create launch template data", func(t *testing.T) {
   919  		g := NewWithT(t)
   920  		scheme, err := setupScheme()
   921  		g.Expect(err).NotTo(HaveOccurred())
   922  		client := fake.NewClientBuilder().WithScheme(scheme).Build()
   923  
   924  		cs, err := setupClusterScope(client)
   925  		g.Expect(err).NotTo(HaveOccurred())
   926  		cs.AWSCluster.Status.Network.SecurityGroups[infrav1.SecurityGroupBastion] = infrav1.SecurityGroup{ID: "1"}
   927  
   928  		ms, err := setupMachinePoolScope(client, cs)
   929  		g.Expect(err).NotTo(HaveOccurred())
   930  
   931  		s := NewService(cs)
   932  
   933  		launchTemplate, err := s.CreateLaunchTemplate(ms, aws.String("imageID"), nil)
   934  		g.Expect(err).To(HaveOccurred())
   935  		g.Expect(launchTemplate).Should(BeEmpty())
   936  	})
   937  }
   938  
   939  func TestCreateLaunchTemplateVersion(t *testing.T) {
   940  	mockCtrl := gomock.NewController(t)
   941  	defer mockCtrl.Finish()
   942  
   943  	var formatTagsInput = func(arg *ec2.CreateLaunchTemplateVersionInput) {
   944  		for index := range arg.LaunchTemplateData.TagSpecifications {
   945  			sortTags(arg.LaunchTemplateData.TagSpecifications[index].Tags)
   946  		}
   947  	}
   948  	var userData = []byte{1, 0, 0}
   949  	testCases := []struct {
   950  		name                 string
   951  		imageID              *string
   952  		awsResourceReference []infrav1.AWSResourceReference
   953  		expect               func(m *mocks.MockEC2APIMockRecorder)
   954  		wantErr              bool
   955  	}{
   956  		{
   957  			name:                 "Should successfully creates launch template version",
   958  			awsResourceReference: []infrav1.AWSResourceReference{{ID: aws.String("1")}},
   959  			expect: func(m *mocks.MockEC2APIMockRecorder) {
   960  				sgMap := make(map[infrav1.SecurityGroupRole]infrav1.SecurityGroup)
   961  				sgMap[infrav1.SecurityGroupNode] = infrav1.SecurityGroup{ID: "1"}
   962  				sgMap[infrav1.SecurityGroupLB] = infrav1.SecurityGroup{ID: "2"}
   963  
   964  				var expectedInput = &ec2.CreateLaunchTemplateVersionInput{
   965  					LaunchTemplateData: &ec2.RequestLaunchTemplateData{
   966  						InstanceType: aws.String("t3.large"),
   967  						IamInstanceProfile: &ec2.LaunchTemplateIamInstanceProfileSpecificationRequest{
   968  							Name: aws.String("instance-profile"),
   969  						},
   970  						KeyName:          aws.String("default"),
   971  						UserData:         pointer.StringPtr(base64.StdEncoding.EncodeToString(userData)),
   972  						SecurityGroupIds: aws.StringSlice([]string{"nodeSG", "lbSG", "1"}),
   973  						ImageId:          aws.String("imageID"),
   974  						TagSpecifications: []*ec2.LaunchTemplateTagSpecificationRequest{
   975  							{
   976  								ResourceType: aws.String(ec2.ResourceTypeInstance),
   977  								Tags:         defaultEC2Tags("aws-mp-name", "cluster-name"),
   978  							},
   979  							{
   980  								ResourceType: aws.String(ec2.ResourceTypeVolume),
   981  								Tags:         defaultEC2Tags("aws-mp-name", "cluster-name"),
   982  							},
   983  						},
   984  					},
   985  					LaunchTemplateId: aws.String("launch-template-id"),
   986  				}
   987  				m.CreateLaunchTemplateVersion(gomock.AssignableToTypeOf(expectedInput)).Return(&ec2.CreateLaunchTemplateVersionOutput{
   988  					LaunchTemplateVersion: &ec2.LaunchTemplateVersion{
   989  						LaunchTemplateId: aws.String("launch-template-id"),
   990  					},
   991  				}, nil).Do(
   992  					func(arg *ec2.CreateLaunchTemplateVersionInput) {
   993  						// formatting added to match tags slice during cmp.Equal()
   994  						formatTagsInput(arg)
   995  						if !cmp.Equal(expectedInput, arg) {
   996  							t.Fatalf("mismatch in input expected: %+v, but got %+v", expectedInput, arg)
   997  						}
   998  					})
   999  			},
  1000  		},
  1001  		{
  1002  			name:                 "Should return error if AWS failed during launch template version creation",
  1003  			awsResourceReference: []infrav1.AWSResourceReference{{ID: aws.String("1")}},
  1004  			expect: func(m *mocks.MockEC2APIMockRecorder) {
  1005  				sgMap := make(map[infrav1.SecurityGroupRole]infrav1.SecurityGroup)
  1006  				sgMap[infrav1.SecurityGroupNode] = infrav1.SecurityGroup{ID: "1"}
  1007  				sgMap[infrav1.SecurityGroupLB] = infrav1.SecurityGroup{ID: "2"}
  1008  
  1009  				var expectedInput = &ec2.CreateLaunchTemplateVersionInput{
  1010  					LaunchTemplateData: &ec2.RequestLaunchTemplateData{
  1011  						InstanceType: aws.String("t3.large"),
  1012  						IamInstanceProfile: &ec2.LaunchTemplateIamInstanceProfileSpecificationRequest{
  1013  							Name: aws.String("instance-profile"),
  1014  						},
  1015  						KeyName:          aws.String("default"),
  1016  						UserData:         pointer.StringPtr(base64.StdEncoding.EncodeToString(userData)),
  1017  						SecurityGroupIds: aws.StringSlice([]string{"nodeSG", "lbSG", "1"}),
  1018  						ImageId:          aws.String("imageID"),
  1019  						TagSpecifications: []*ec2.LaunchTemplateTagSpecificationRequest{
  1020  							{
  1021  								ResourceType: aws.String(ec2.ResourceTypeInstance),
  1022  								Tags:         defaultEC2Tags("aws-mp-name", "cluster-name"),
  1023  							},
  1024  							{
  1025  								ResourceType: aws.String(ec2.ResourceTypeVolume),
  1026  								Tags:         defaultEC2Tags("aws-mp-name", "cluster-name"),
  1027  							},
  1028  						},
  1029  					},
  1030  					LaunchTemplateId: aws.String("launch-template-id"),
  1031  				}
  1032  				m.CreateLaunchTemplateVersion(gomock.AssignableToTypeOf(expectedInput)).Return(nil,
  1033  					awserrors.NewFailedDependency("dependency failure")).Do(
  1034  					func(arg *ec2.CreateLaunchTemplateVersionInput) {
  1035  						// formatting added to match tags slice during cmp.Equal()
  1036  						formatTagsInput(arg)
  1037  						if !cmp.Equal(expectedInput, arg) {
  1038  							t.Fatalf("mismatch in input expected: %+v, got: %+v", expectedInput, arg)
  1039  						}
  1040  					})
  1041  			},
  1042  			wantErr: true,
  1043  		},
  1044  	}
  1045  	for _, tc := range testCases {
  1046  		t.Run(tc.name, func(t *testing.T) {
  1047  			g := NewWithT(t)
  1048  
  1049  			scheme, err := setupScheme()
  1050  			g.Expect(err).NotTo(HaveOccurred())
  1051  			client := fake.NewClientBuilder().WithScheme(scheme).Build()
  1052  
  1053  			cs, err := setupClusterScope(client)
  1054  			g.Expect(err).NotTo(HaveOccurred())
  1055  
  1056  			mpScope, err := setupMachinePoolScope(client, cs)
  1057  			g.Expect(err).NotTo(HaveOccurred())
  1058  
  1059  			mpScope.AWSMachinePool.Spec.AWSLaunchTemplate.AdditionalSecurityGroups = tc.awsResourceReference
  1060  
  1061  			mockEC2Client := mocks.NewMockEC2API(mockCtrl)
  1062  			s := NewService(cs)
  1063  			s.EC2Client = mockEC2Client
  1064  
  1065  			if tc.expect != nil {
  1066  				tc.expect(mockEC2Client.EXPECT())
  1067  			}
  1068  			if tc.wantErr {
  1069  				g.Expect(s.CreateLaunchTemplateVersion(mpScope, aws.String("imageID"), userData)).To(HaveOccurred())
  1070  				return
  1071  			}
  1072  			g.Expect(s.CreateLaunchTemplateVersion(mpScope, aws.String("imageID"), userData)).NotTo(HaveOccurred())
  1073  		})
  1074  	}
  1075  }
  1076  
  1077  func TestBuildLaunchTemplateTagSpecificationRequest(t *testing.T) {
  1078  	mockCtrl := gomock.NewController(t)
  1079  	defer mockCtrl.Finish()
  1080  
  1081  	testCases := []struct {
  1082  		name  string
  1083  		check func(g *WithT, m []*ec2.LaunchTemplateTagSpecificationRequest)
  1084  	}{
  1085  		{
  1086  			name: "Should create tag specification request for building Launch template tags",
  1087  			check: func(g *WithT, res []*ec2.LaunchTemplateTagSpecificationRequest) {
  1088  				expected := []*ec2.LaunchTemplateTagSpecificationRequest{
  1089  					{
  1090  						ResourceType: aws.String(ec2.ResourceTypeInstance),
  1091  						Tags:         defaultEC2Tags("aws-mp-name", "cluster-name"),
  1092  					},
  1093  					{
  1094  						ResourceType: aws.String(ec2.ResourceTypeVolume),
  1095  						Tags:         defaultEC2Tags("aws-mp-name", "cluster-name"),
  1096  					},
  1097  				}
  1098  				// sorting tags for comparing each request tags during cmp.Equal()
  1099  				for _, each := range res {
  1100  					sortTags(each.Tags)
  1101  				}
  1102  				g.Expect(res).Should(Equal(expected))
  1103  			},
  1104  		},
  1105  	}
  1106  	for _, tc := range testCases {
  1107  		t.Run(tc.name, func(t *testing.T) {
  1108  			g := NewWithT(t)
  1109  
  1110  			scheme, err := setupScheme()
  1111  			g.Expect(err).NotTo(HaveOccurred())
  1112  			client := fake.NewClientBuilder().WithScheme(scheme).Build()
  1113  
  1114  			cs, err := setupClusterScope(client)
  1115  			g.Expect(err).NotTo(HaveOccurred())
  1116  
  1117  			mpScope, err := setupMachinePoolScope(client, cs)
  1118  			g.Expect(err).NotTo(HaveOccurred())
  1119  
  1120  			s := NewService(cs)
  1121  			tc.check(g, s.buildLaunchTemplateTagSpecificationRequest(mpScope))
  1122  		})
  1123  	}
  1124  }
  1125  
  1126  func TestDiscoverLaunchTemplateAMI(t *testing.T) {
  1127  	mockCtrl := gomock.NewController(t)
  1128  	defer mockCtrl.Finish()
  1129  
  1130  	testCases := []struct {
  1131  		name              string
  1132  		awsLaunchTemplate expinfrav1.AWSLaunchTemplate
  1133  		machineTemplate   clusterv1.MachineTemplateSpec
  1134  		expect            func(m *mocks.MockEC2APIMockRecorder)
  1135  		check             func(*WithT, *string, error)
  1136  	}{
  1137  		{
  1138  			name: "Should return default AMI for non EKS managed cluster if Image lookup format, org and BaseOS passed",
  1139  			awsLaunchTemplate: expinfrav1.AWSLaunchTemplate{
  1140  				Name:              "aws-launch-tmpl",
  1141  				ImageLookupFormat: "ilf",
  1142  				ImageLookupOrg:    "ilo",
  1143  				ImageLookupBaseOS: "ilbo",
  1144  			},
  1145  			machineTemplate: clusterv1.MachineTemplateSpec{
  1146  				Spec: clusterv1.MachineSpec{
  1147  					Version: aws.String(DefaultAmiNameFormat),
  1148  				},
  1149  			},
  1150  			expect: func(m *mocks.MockEC2APIMockRecorder) {
  1151  				m.DescribeImages(gomock.AssignableToTypeOf(&ec2.DescribeImagesInput{})).
  1152  					Return(&ec2.DescribeImagesOutput{
  1153  						Images: []*ec2.Image{
  1154  							{
  1155  								ImageId:      aws.String("ancient"),
  1156  								CreationDate: aws.String("2011-02-08T17:02:31.000Z"),
  1157  							},
  1158  							{
  1159  								ImageId:      aws.String("latest"),
  1160  								CreationDate: aws.String("2019-02-08T17:02:31.000Z"),
  1161  							},
  1162  							{
  1163  								ImageId:      aws.String("oldest"),
  1164  								CreationDate: aws.String("2014-02-08T17:02:31.000Z"),
  1165  							},
  1166  						},
  1167  					}, nil)
  1168  			},
  1169  			check: func(g *WithT, res *string, err error) {
  1170  				g.Expect(res).Should(Equal(aws.String("latest")))
  1171  				g.Expect(err).NotTo(HaveOccurred())
  1172  			},
  1173  		},
  1174  		{
  1175  			name: "Should return AMI and use infra cluster image details, if not passed in aws launchtemplate",
  1176  			awsLaunchTemplate: expinfrav1.AWSLaunchTemplate{
  1177  				Name: "aws-launch-tmpl",
  1178  			},
  1179  			machineTemplate: clusterv1.MachineTemplateSpec{
  1180  				Spec: clusterv1.MachineSpec{
  1181  					Version: aws.String(DefaultAmiNameFormat),
  1182  				},
  1183  			},
  1184  			expect: func(m *mocks.MockEC2APIMockRecorder) {
  1185  				m.DescribeImages(gomock.AssignableToTypeOf(&ec2.DescribeImagesInput{})).
  1186  					Return(&ec2.DescribeImagesOutput{
  1187  						Images: []*ec2.Image{
  1188  							{
  1189  								ImageId:      aws.String("ancient"),
  1190  								CreationDate: aws.String("2011-02-08T17:02:31.000Z"),
  1191  							},
  1192  							{
  1193  								ImageId:      aws.String("latest"),
  1194  								CreationDate: aws.String("2019-02-08T17:02:31.000Z"),
  1195  							},
  1196  							{
  1197  								ImageId:      aws.String("oldest"),
  1198  								CreationDate: aws.String("2014-02-08T17:02:31.000Z"),
  1199  							},
  1200  						},
  1201  					}, nil)
  1202  			},
  1203  			check: func(g *WithT, res *string, err error) {
  1204  				g.Expect(res).Should(Equal(aws.String("latest")))
  1205  				g.Expect(err).NotTo(HaveOccurred())
  1206  			},
  1207  		},
  1208  		{
  1209  			name: "Should return AWSlaunchtemplate ID if provided",
  1210  			awsLaunchTemplate: expinfrav1.AWSLaunchTemplate{
  1211  				Name: "aws-launch-tmpl",
  1212  				AMI:  infrav1.AMIReference{ID: aws.String("id")},
  1213  			},
  1214  			check: func(g *WithT, res *string, err error) {
  1215  				g.Expect(res).Should(Equal(aws.String("id")))
  1216  				g.Expect(err).NotTo(HaveOccurred())
  1217  			},
  1218  		},
  1219  		{
  1220  			name: "Should return with error if both AWSlaunchtemplate ID and machinePool version is not provided",
  1221  			awsLaunchTemplate: expinfrav1.AWSLaunchTemplate{
  1222  				Name: "aws-launch-tmpl",
  1223  			},
  1224  			check: func(g *WithT, res *string, err error) {
  1225  				g.Expect(err).To(HaveOccurred())
  1226  				g.Expect(res).To(BeNil())
  1227  			},
  1228  		},
  1229  		{
  1230  			name: "Should return error if AWS failed while describing images",
  1231  			awsLaunchTemplate: expinfrav1.AWSLaunchTemplate{
  1232  				Name: "aws-launch-tmpl",
  1233  			},
  1234  			machineTemplate: clusterv1.MachineTemplateSpec{
  1235  				Spec: clusterv1.MachineSpec{
  1236  					Version: aws.String(DefaultAmiNameFormat),
  1237  				},
  1238  			},
  1239  			expect: func(m *mocks.MockEC2APIMockRecorder) {
  1240  				m.DescribeImages(gomock.AssignableToTypeOf(&ec2.DescribeImagesInput{})).
  1241  					Return(nil, awserrors.NewFailedDependency("dependency-failure"))
  1242  			},
  1243  			check: func(g *WithT, res *string, err error) {
  1244  				g.Expect(res).To(BeNil())
  1245  				g.Expect(err).To(HaveOccurred())
  1246  			},
  1247  		},
  1248  	}
  1249  	for _, tc := range testCases {
  1250  		t.Run(tc.name, func(t *testing.T) {
  1251  			g := NewWithT(t)
  1252  
  1253  			ec2Mock := mocks.NewMockEC2API(mockCtrl)
  1254  
  1255  			scheme, err := setupScheme()
  1256  			g.Expect(err).NotTo(HaveOccurred())
  1257  			client := fake.NewClientBuilder().WithScheme(scheme).Build()
  1258  
  1259  			cs, err := setupClusterScope(client)
  1260  			g.Expect(err).NotTo(HaveOccurred())
  1261  
  1262  			ms, err := setupMachinePoolScope(client, cs)
  1263  			g.Expect(err).NotTo(HaveOccurred())
  1264  
  1265  			ms.AWSMachinePool.Spec.AWSLaunchTemplate = tc.awsLaunchTemplate
  1266  			ms.MachinePool.Spec.Template = tc.machineTemplate
  1267  
  1268  			if tc.expect != nil {
  1269  				tc.expect(ec2Mock.EXPECT())
  1270  			}
  1271  
  1272  			s := NewService(cs)
  1273  			s.EC2Client = ec2Mock
  1274  
  1275  			id, err := s.DiscoverLaunchTemplateAMI(ms)
  1276  			tc.check(g, id, err)
  1277  		})
  1278  	}
  1279  }
  1280  
  1281  func TestDiscoverLaunchTemplateAMI_ForEKS(t *testing.T) {
  1282  	mockCtrl := gomock.NewController(t)
  1283  	defer mockCtrl.Finish()
  1284  
  1285  	testCases := []struct {
  1286  		name              string
  1287  		awsLaunchTemplate expinfrav1.AWSLaunchTemplate
  1288  		machineTemplate   clusterv1.MachineTemplateSpec
  1289  		expect            func(m *mock_ssmiface.MockSSMAPIMockRecorder)
  1290  		check             func(*WithT, *string, error)
  1291  	}{
  1292  		{
  1293  			name: "Should return AMI and use EKS infra cluster image details, if not passed in aws launch template",
  1294  			expect: func(m *mock_ssmiface.MockSSMAPIMockRecorder) {
  1295  				m.GetParameter(gomock.AssignableToTypeOf(&ssm.GetParameterInput{})).
  1296  					Return(&ssm.GetParameterOutput{
  1297  						Parameter: &ssm.Parameter{
  1298  							Value: aws.String("latest"),
  1299  						},
  1300  					}, nil)
  1301  			},
  1302  			check: func(g *WithT, res *string, err error) {
  1303  				g.Expect(res).Should(Equal(aws.String("latest")))
  1304  				g.Expect(err).NotTo(HaveOccurred())
  1305  			},
  1306  		},
  1307  	}
  1308  	for _, tc := range testCases {
  1309  		t.Run(tc.name, func(t *testing.T) {
  1310  			g := NewWithT(t)
  1311  
  1312  			ssmMock := mock_ssmiface.NewMockSSMAPI(mockCtrl)
  1313  
  1314  			scheme, err := setupScheme()
  1315  			g.Expect(err).NotTo(HaveOccurred())
  1316  			client := fake.NewClientBuilder().WithScheme(scheme).Build()
  1317  
  1318  			mcps, err := setupNewManagedControlPlaneScope(client)
  1319  			g.Expect(err).NotTo(HaveOccurred())
  1320  
  1321  			ms, err := setupMachinePoolScope(client, mcps)
  1322  			g.Expect(err).NotTo(HaveOccurred())
  1323  
  1324  			if tc.expect != nil {
  1325  				tc.expect(ssmMock.EXPECT())
  1326  			}
  1327  
  1328  			s := NewService(mcps)
  1329  			s.SSMClient = ssmMock
  1330  
  1331  			id, err := s.DiscoverLaunchTemplateAMI(ms)
  1332  			tc.check(g, id, err)
  1333  		})
  1334  	}
  1335  }
  1336  
  1337  func TestDeleteLaunchTemplateVersion(t *testing.T) {
  1338  	mockCtrl := gomock.NewController(t)
  1339  	defer mockCtrl.Finish()
  1340  
  1341  	type args struct {
  1342  		id      string
  1343  		version *int64
  1344  	}
  1345  	testCases := []struct {
  1346  		name    string
  1347  		args    args
  1348  		expect  func(m *mocks.MockEC2APIMockRecorder)
  1349  		wantErr bool
  1350  	}{
  1351  		{
  1352  			name:    "Should return error if version is nil",
  1353  			wantErr: true,
  1354  		},
  1355  		{
  1356  			name: "Should return error if AWS unable to delete launch template version",
  1357  			args: args{
  1358  				id:      "id",
  1359  				version: aws.Int64(12),
  1360  			},
  1361  			expect: func(m *mocks.MockEC2APIMockRecorder) {
  1362  				m.DeleteLaunchTemplateVersions(gomock.Eq(
  1363  					&ec2.DeleteLaunchTemplateVersionsInput{
  1364  						LaunchTemplateId: aws.String("id"),
  1365  						Versions:         aws.StringSlice([]string{"12"}),
  1366  					},
  1367  				)).Return(nil, awserrors.NewFailedDependency("dependency-failure"))
  1368  			},
  1369  			wantErr: true,
  1370  		},
  1371  		{
  1372  			name: "Should successfully deletes launch template version if AWS call passed",
  1373  			args: args{
  1374  				id:      "id",
  1375  				version: aws.Int64(12),
  1376  			},
  1377  			expect: func(m *mocks.MockEC2APIMockRecorder) {
  1378  				m.DeleteLaunchTemplateVersions(gomock.Eq(
  1379  					&ec2.DeleteLaunchTemplateVersionsInput{
  1380  						LaunchTemplateId: aws.String("id"),
  1381  						Versions:         aws.StringSlice([]string{"12"}),
  1382  					},
  1383  				)).Return(nil, nil)
  1384  			},
  1385  		},
  1386  	}
  1387  
  1388  	for _, tc := range testCases {
  1389  		t.Run(tc.name, func(t *testing.T) {
  1390  			g := NewWithT(t)
  1391  
  1392  			scheme, err := setupScheme()
  1393  			g.Expect(err).NotTo(HaveOccurred())
  1394  			client := fake.NewClientBuilder().WithScheme(scheme).Build()
  1395  
  1396  			cs, err := setupClusterScope(client)
  1397  			g.Expect(err).NotTo(HaveOccurred())
  1398  
  1399  			ec2Mock := mocks.NewMockEC2API(mockCtrl)
  1400  			s := NewService(cs)
  1401  			s.EC2Client = ec2Mock
  1402  
  1403  			if tc.expect != nil {
  1404  				tc.expect(ec2Mock.EXPECT())
  1405  			}
  1406  
  1407  			if tc.wantErr {
  1408  				g.Expect(s.deleteLaunchTemplateVersion(tc.args.id, tc.args.version)).To(HaveOccurred())
  1409  				return
  1410  			}
  1411  			g.Expect(s.deleteLaunchTemplateVersion(tc.args.id, tc.args.version)).NotTo(HaveOccurred())
  1412  		})
  1413  	}
  1414  }