From e8a768f145bb139e7f17a874537b1ee47fd0daa6 Mon Sep 17 00:00:00 2001 From: Leah Dibble Date: Fri, 27 Sep 2024 13:31:19 -0700 Subject: [PATCH 1/5] InstanceProfile V2 changes --- go.mod | 23 ++- go.sum | 36 +++++ pkg/aws/sdk/sdk.go | 70 +++++++++ .../nodeclass/status/instanceprofile_test.go | 32 ++-- .../nodeclass/termination/suite_test.go | 32 ++-- pkg/errors/errors.go | 30 ++++ pkg/fake/iamapi.go | 138 +++++++++++++----- pkg/operator/operator.go | 54 ++++++- .../instanceprofile/instanceprofile.go | 49 ++++--- 9 files changed, 365 insertions(+), 99 deletions(-) create mode 100644 pkg/aws/sdk/sdk.go diff --git a/go.mod b/go.mod index c0a10b58a8f4..114097177773 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,22 @@ module github.com/aws/karpenter-provider-aws -go 1.23.0 +go 1.23.1 require ( github.com/Pallinder/go-randomdata v1.2.0 github.com/PuerkitoBio/goquery v1.10.0 github.com/avast/retry-go v3.0.0+incompatible github.com/aws/aws-sdk-go v1.55.5 + github.com/aws/aws-sdk-go-v2 v1.31.0 + github.com/aws/aws-sdk-go-v2/config v1.26.6 + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 + github.com/aws/aws-sdk-go-v2/service/ec2 v1.177.3 + github.com/aws/aws-sdk-go-v2/service/eks v1.48.3 + github.com/aws/aws-sdk-go-v2/service/iam v1.36.2 + github.com/aws/aws-sdk-go-v2/service/pricing v1.30.5 + github.com/aws/aws-sdk-go-v2/service/ssm v1.52.7 github.com/aws/karpenter-provider-aws/tools/kompat v0.0.0-20240410220356-6b868db24881 + github.com/aws/smithy-go v1.21.0 github.com/awslabs/amazon-eks-ami/nodeadm v0.0.0-20240229193347-cfab22a10647 github.com/awslabs/operatorpkg v0.0.0-20240920182301-771460b3160b github.com/go-logr/zapr v1.3.0 @@ -35,6 +44,18 @@ require ( sigs.k8s.io/yaml v1.4.0 ) +require ( + github.com/aws/aws-sdk-go-v2/credentials v1.16.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect +) + require ( github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/andybalholm/cascadia v1.3.2 // indirect diff --git a/go.sum b/go.sum index 7dcd2f4579e7..cb69e822497d 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,44 @@ github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHS github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go-v2 v1.31.0 h1:3V05LbxTSItI5kUqNwhJrrrY1BAXxXt0sN0l72QmG5U= +github.com/aws/aws-sdk-go-v2 v1.31.0/go.mod h1:ztolYtaEUtdpf9Wftr31CJfLVjOnD/CVRkKOOYgF8hA= +github.com/aws/aws-sdk-go-v2/config v1.26.6 h1:Z/7w9bUqlRI0FFQpetVuFYEsjzE3h7fpU6HuGmfPL/o= +github.com/aws/aws-sdk-go-v2/config v1.26.6/go.mod h1:uKU6cnDmYCvJ+pxO9S4cWDb2yWWIH5hra+32hVh1MI4= +github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5gbadPbWdV1WcAddK8= +github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 h1:kYQ3H1u0ANr9KEKlGs/jTLrBFPo8P8NaH/w7A01NeeM= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18/go.mod h1:r506HmK5JDUh9+Mw4CfGJGSSoqIiLCndAuqXuhbv67Y= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 h1:Z7IdFUONvTcvS7YuhtVxN99v2cCoHRXOS4mTr0B/pUc= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18/go.mod h1:DkKMmksZVVyat+Y+r1dEOgJEfUeA7UngIHWeKsi0yNc= +github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3 h1:n3GDfwqF2tzEkXlv5cuy4iy7LpKDtqDMcNLfZDu9rls= +github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.177.3 h1:dqdCh1M8h+j8OGNUpxTs7eBPFr6lOdLpdlE6IPLLSq4= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.177.3/go.mod h1:TFSALWR7Xs7+KyMM87ZAYxncKFBvzEt2rpK/BJCH2ps= +github.com/aws/aws-sdk-go-v2/service/eks v1.48.3 h1:aGmoZ5b8ZUYMZ44lKRON/n2pylB6v/OmuutkX4LEL64= +github.com/aws/aws-sdk-go-v2/service/eks v1.48.3/go.mod h1:9dn8p15siUL80NCTPVNd+YvEpVTmWO+rboGx6qOMBa0= +github.com/aws/aws-sdk-go-v2/service/iam v1.36.2 h1:2/kSYD8hfRU/q1HbgSzZ4PGiDmzDwtPSYgJq4yxF6bs= +github.com/aws/aws-sdk-go-v2/service/iam v1.36.2/go.mod h1:HSvujsK8xeEHMIB18oMXjSfqaN9cVqpo/MtHJIksQRk= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 h1:KypMCbLPPHEmf9DgMGw51jMj77VfGPAN2Kv4cfhlfgI= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4/go.mod h1:Vz1JQXliGcQktFTN/LN6uGppAIRoLBR2bMvIMP0gOjc= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 h1:rfprUlsdzgl7ZL2KlXiUAoJnI/VxfHCvDFr2QDFj6u4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19/go.mod h1:SCWkEdRq8/7EK60NcvvQ6NXKuTcchAD4ROAsC37VEZE= +github.com/aws/aws-sdk-go-v2/service/pricing v1.30.5 h1:Yr3T1tExevxUSylr1jb2BjGKnefy68RsPR+7gZmNGKk= +github.com/aws/aws-sdk-go-v2/service/pricing v1.30.5/go.mod h1:s25xxxgOUJZAyvM3hlt/HKIK8OQa3U+G8dyZpUFSYDU= +github.com/aws/aws-sdk-go-v2/service/ssm v1.52.7 h1:1UfdVi84wNLY2GZrp3fxldNemzVQ+uA2Nke7kZ6Nwik= +github.com/aws/aws-sdk-go-v2/service/ssm v1.52.7/go.mod h1:nUSNPaG8mv5rIu7EclHnFqZOjhreEUwRKENtKTtJ9aw= +github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow= +github.com/aws/aws-sdk-go-v2/service/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8= +github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0= +github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U= github.com/aws/karpenter-provider-aws/tools/kompat v0.0.0-20240410220356-6b868db24881 h1:m9rhsGhdepdQV96tZgfy68oU75AWAjOH8u65OefTjwA= github.com/aws/karpenter-provider-aws/tools/kompat v0.0.0-20240410220356-6b868db24881/go.mod h1:+Mk5k0b6HpKobxNq+B56DOhZ+I/NiPhd5MIBhQMSTSs= +github.com/aws/smithy-go v1.21.0 h1:H7L8dtDRk0P1Qm6y0ji7MCYMQObJ5R9CRpyPhRUkLYA= +github.com/aws/smithy-go v1.21.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/awslabs/amazon-eks-ami/nodeadm v0.0.0-20240229193347-cfab22a10647 h1:8yRBVsjGmI7qQsPWtIrbWP+XfwHO9Wq7gdLVzjqiZFs= github.com/awslabs/amazon-eks-ami/nodeadm v0.0.0-20240229193347-cfab22a10647/go.mod h1:9NafTAUHL0FlMeL6Cu5PXnMZ1q/LnC9X2emLXHsVbM8= github.com/awslabs/operatorpkg v0.0.0-20240920182301-771460b3160b h1:aG1+YRmKIf5nLTZJNhw1NmuxvjUprWYyluqJ2jmVqiU= diff --git a/pkg/aws/sdk/sdk.go b/pkg/aws/sdk/sdk.go new file mode 100644 index 000000000000..f71b417363fb --- /dev/null +++ b/pkg/aws/sdk/sdk.go @@ -0,0 +1,70 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package sdk + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/service/ec2" + "github.com/aws/aws-sdk-go-v2/service/eks" + "github.com/aws/aws-sdk-go-v2/service/iam" + "github.com/aws/aws-sdk-go-v2/service/pricing" + "github.com/aws/aws-sdk-go-v2/service/ssm" +) + +type EC2API interface { + // EC2 Methods + DescribeImages(context.Context, *ec2.DescribeImagesInput, ...func(*ec2.Options)) (*ec2.DescribeImagesOutput, error) + DescribeLaunchTemplates(context.Context, *ec2.DescribeLaunchTemplatesInput, ...func(*ec2.Options)) (*ec2.DescribeLaunchTemplatesOutput, error) + DescribeSubnets(context.Context, *ec2.DescribeSubnetsInput, ...func(*ec2.Options)) (*ec2.DescribeSubnetsOutput, error) + DescribeSecurityGroups(context.Context, *ec2.DescribeSecurityGroupsInput, ...func(*ec2.Options)) (*ec2.DescribeSecurityGroupsOutput, error) + DescribeInstanceTypes(context.Context, *ec2.DescribeInstanceTypesInput, ...func(*ec2.Options)) (*ec2.DescribeInstanceTypesOutput, error) + DescribeInstanceTypesWithContext(context.Context, *ec2.DescribeInstanceTypesInput, ...func(*ec2.Options)) (*ec2.DescribeInstanceTypesOutput, error) + DescribeInstanceTypeOfferings(context.Context, *ec2.DescribeInstanceTypeOfferingsInput, ...func(*ec2.Options)) (*ec2.DescribeInstanceTypeOfferingsOutput, error) + DescribeAvailabilityZones(context.Context, *ec2.DescribeAvailabilityZonesInput, ...func(*ec2.Options)) (*ec2.DescribeAvailabilityZonesOutput, error) + DescribeSpotPriceHistory(context.Context, *ec2.DescribeSpotPriceHistoryInput, ...func(*ec2.Options)) (*ec2.DescribeSpotPriceHistoryOutput, error) + CreateFleet(context.Context, *ec2.CreateFleetInput, ...func(*ec2.Options)) (*ec2.CreateFleetOutput, error) + TerminateInstances(context.Context, *ec2.TerminateInstancesInput, ...func(*ec2.Options)) (*ec2.TerminateInstancesOutput, error) + DescribeInstances(context.Context, *ec2.DescribeInstancesInput, ...func(*ec2.Options)) (*ec2.DescribeInstancesOutput, error) + CreateTags(context.Context, *ec2.CreateTagsInput, ...func(*ec2.Options)) (*ec2.CreateTagsOutput, error) + CreateLaunchTemplate(context.Context, *ec2.CreateLaunchTemplateInput, ...func(*ec2.Options)) (*ec2.CreateLaunchTemplateOutput, error) + DeleteLaunchTemplate(context.Context, *ec2.DeleteLaunchTemplateInput, ...func(*ec2.Options)) (*ec2.DeleteLaunchTemplateOutput, error) +} + +type IAMAPI interface { + // IAM Methods + GetInstanceProfile(context.Context, *iam.GetInstanceProfileInput, ...func(*iam.Options)) (*iam.GetInstanceProfileOutput, error) + CreateInstanceProfile(context.Context, *iam.CreateInstanceProfileInput, ...func(*iam.Options)) (*iam.CreateInstanceProfileOutput, error) + DeleteInstanceProfile(context.Context, *iam.DeleteInstanceProfileInput, ...func(*iam.Options)) (*iam.DeleteInstanceProfileOutput, error) + AddRoleToInstanceProfile(context.Context, *iam.AddRoleToInstanceProfileInput, ...func(*iam.Options)) (*iam.AddRoleToInstanceProfileOutput, error) + TagInstanceProfile(context.Context, *iam.TagInstanceProfileInput, ...func(*iam.Options)) (*iam.TagInstanceProfileOutput, error) + RemoveRoleFromInstanceProfile(context.Context, *iam.RemoveRoleFromInstanceProfileInput, ...func(*iam.Options)) (*iam.RemoveRoleFromInstanceProfileOutput, error) + UntagInstanceProfile(context.Context, *iam.UntagInstanceProfileInput, ...func(*iam.Options)) (*iam.UntagInstanceProfileOutput, error) +} + +type EKSAPI interface { + // EKS Methods + DescribeCluster(context.Context, *eks.DescribeClusterInput, ...func(*eks.Options)) (*eks.DescribeClusterOutput, error) + DescribeClusterWithContext(context.Context, *eks.DescribeClusterInput, ...func(*eks.Options)) (*eks.DescribeClusterOutput, error) +} + +type PricingAPI interface { + // Pricing Methods + GetProducts(context.Context, *pricing.GetProductsInput, ...func(*pricing.Options)) (*pricing.GetProductsOutput, error) +} + +type SSMAPI interface { + // SSM Methods + GetParameter(context.Context, *ssm.GetParameterInput, ...func(*ssm.Options)) (*ssm.GetParameterOutput, error) +} diff --git a/pkg/controllers/nodeclass/status/instanceprofile_test.go b/pkg/controllers/nodeclass/status/instanceprofile_test.go index 75f3fa7df1ad..2f0f342eb863 100644 --- a/pkg/controllers/nodeclass/status/instanceprofile_test.go +++ b/pkg/controllers/nodeclass/status/instanceprofile_test.go @@ -17,8 +17,8 @@ package status_test import ( "fmt" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/iam" + "github.com/aws/aws-sdk-go-v2/aws" + iamtypes "github.com/aws/aws-sdk-go-v2/service/iam/types" "github.com/samber/lo" v1 "github.com/aws/karpenter-provider-aws/pkg/apis/v1" @@ -45,9 +45,9 @@ var _ = Describe("NodeClass InstanceProfile Status Controller", func() { Expect(awsEnv.IAMAPI.InstanceProfiles[profileName].Roles).To(HaveLen(1)) Expect(*awsEnv.IAMAPI.InstanceProfiles[profileName].Roles[0].RoleName).To(Equal("test-role")) Expect(awsEnv.IAMAPI.InstanceProfiles[profileName].Tags).To(ContainElements( - &iam.Tag{Key: lo.ToPtr(fmt.Sprintf("kubernetes.io/cluster/%s", options.FromContext(ctx).ClusterName)), Value: lo.ToPtr("owned")}, - &iam.Tag{Key: lo.ToPtr(v1.LabelNodeClass), Value: lo.ToPtr(nodeClass.Name)}, - &iam.Tag{Key: lo.ToPtr(v1.EKSClusterNameTagKey), Value: lo.ToPtr(options.FromContext(ctx).ClusterName)}, + iamtypes.Tag{Key: lo.ToPtr(fmt.Sprintf("kubernetes.io/cluster/%s", options.FromContext(ctx).ClusterName)), Value: lo.ToPtr("owned")}, + iamtypes.Tag{Key: lo.ToPtr(v1.LabelNodeClass), Value: lo.ToPtr(nodeClass.Name)}, + iamtypes.Tag{Key: lo.ToPtr(v1.EKSClusterNameTagKey), Value: lo.ToPtr(options.FromContext(ctx).ClusterName)}, )) nodeClass = ExpectExists(ctx, env.Client, nodeClass) @@ -55,7 +55,7 @@ var _ = Describe("NodeClass InstanceProfile Status Controller", func() { Expect(nodeClass.StatusConditions().IsTrue(v1.ConditionTypeInstanceProfileReady)).To(BeTrue()) }) It("should add the role to the instance profile when it exists without a role", func() { - awsEnv.IAMAPI.InstanceProfiles = map[string]*iam.InstanceProfile{ + awsEnv.IAMAPI.InstanceProfiles = map[string]*iamtypes.InstanceProfile{ profileName: { InstanceProfileId: aws.String(fake.InstanceProfileID()), InstanceProfileName: aws.String(profileName), @@ -75,11 +75,11 @@ var _ = Describe("NodeClass InstanceProfile Status Controller", func() { Expect(nodeClass.StatusConditions().IsTrue(v1.ConditionTypeInstanceProfileReady)).To(BeTrue()) }) It("should update the role for the instance profile when the wrong role exists", func() { - awsEnv.IAMAPI.InstanceProfiles = map[string]*iam.InstanceProfile{ + awsEnv.IAMAPI.InstanceProfiles = map[string]*iamtypes.InstanceProfile{ profileName: { InstanceProfileId: aws.String(fake.InstanceProfileID()), InstanceProfileName: aws.String(profileName), - Roles: []*iam.Role{ + Roles: []iamtypes.Role{ { RoleName: aws.String("other-role"), }, @@ -100,16 +100,16 @@ var _ = Describe("NodeClass InstanceProfile Status Controller", func() { Expect(nodeClass.StatusConditions().IsTrue(v1.ConditionTypeInstanceProfileReady)).To(BeTrue()) }) It("should add the eks:eks-cluster-name tag when the tag doesn't exist", func() { - awsEnv.IAMAPI.InstanceProfiles = map[string]*iam.InstanceProfile{ + awsEnv.IAMAPI.InstanceProfiles = map[string]*iamtypes.InstanceProfile{ profileName: { InstanceProfileId: aws.String(fake.InstanceProfileID()), InstanceProfileName: aws.String(profileName), - Roles: []*iam.Role{ + Roles: []iamtypes.Role{ { RoleName: aws.String("other-role"), }, }, - Tags: []*iam.Tag{ + Tags: []iamtypes.Tag{ { Key: lo.ToPtr(fmt.Sprintf("kubernetes.io/cluster/%s", options.FromContext(ctx).ClusterName)), Value: lo.ToPtr("owned"), @@ -127,17 +127,17 @@ var _ = Describe("NodeClass InstanceProfile Status Controller", func() { Expect(awsEnv.IAMAPI.InstanceProfiles).To(HaveLen(1)) Expect(awsEnv.IAMAPI.InstanceProfiles[profileName].Tags).To(ContainElements( - &iam.Tag{Key: lo.ToPtr(fmt.Sprintf("kubernetes.io/cluster/%s", options.FromContext(ctx).ClusterName)), Value: lo.ToPtr("owned")}, - &iam.Tag{Key: lo.ToPtr(v1.LabelNodeClass), Value: lo.ToPtr(nodeClass.Name)}, - &iam.Tag{Key: lo.ToPtr(v1.EKSClusterNameTagKey), Value: lo.ToPtr(options.FromContext(ctx).ClusterName)}, + iamtypes.Tag{Key: lo.ToPtr(fmt.Sprintf("kubernetes.io/cluster/%s", options.FromContext(ctx).ClusterName)), Value: lo.ToPtr("owned")}, + iamtypes.Tag{Key: lo.ToPtr(v1.LabelNodeClass), Value: lo.ToPtr(nodeClass.Name)}, + iamtypes.Tag{Key: lo.ToPtr(v1.EKSClusterNameTagKey), Value: lo.ToPtr(options.FromContext(ctx).ClusterName)}, )) }) It("should not call CreateInstanceProfile or AddRoleToInstanceProfile when instance profile exists with correct role", func() { - awsEnv.IAMAPI.InstanceProfiles = map[string]*iam.InstanceProfile{ + awsEnv.IAMAPI.InstanceProfiles = map[string]*iamtypes.InstanceProfile{ profileName: { InstanceProfileId: aws.String(fake.InstanceProfileID()), InstanceProfileName: aws.String(profileName), - Roles: []*iam.Role{ + Roles: []iamtypes.Role{ { RoleName: aws.String("test-role"), }, diff --git a/pkg/controllers/nodeclass/termination/suite_test.go b/pkg/controllers/nodeclass/termination/suite_test.go index c24f42121d6f..adbde65f02ca 100644 --- a/pkg/controllers/nodeclass/termination/suite_test.go +++ b/pkg/controllers/nodeclass/termination/suite_test.go @@ -22,9 +22,13 @@ import ( "sigs.k8s.io/karpenter/pkg/test/v1alpha1" - "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go-v2/aws" + ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" + iamtypes "github.com/aws/aws-sdk-go-v2/service/iam/types" + + //used for launch template tests until they are migrated "github.com/aws/aws-sdk-go/service/ec2" - "github.com/aws/aws-sdk-go/service/iam" + "github.com/awslabs/operatorpkg/object" "github.com/samber/lo" "k8s.io/client-go/tools/record" @@ -109,7 +113,7 @@ var _ = Describe("NodeClass Termination", func() { }) It("should not delete the NodeClass if launch template deletion fails", func() { launchTemplateName := aws.String(fake.LaunchTemplateName()) - awsEnv.EC2API.LaunchTemplates.Store(launchTemplateName, &ec2.LaunchTemplate{LaunchTemplateName: launchTemplateName, LaunchTemplateId: aws.String(fake.LaunchTemplateID()), Tags: []*ec2.Tag{&ec2.Tag{Key: aws.String("karpenter.k8s.aws/cluster"), Value: aws.String("test-cluster")}}}) + awsEnv.EC2API.LaunchTemplates.Store(launchTemplateName, &ec2types.LaunchTemplate{LaunchTemplateName: launchTemplateName, LaunchTemplateId: aws.String(fake.LaunchTemplateID()), Tags: []ec2types.Tag{{Key: aws.String("karpenter.k8s.aws/cluster"), Value: aws.String("test-cluster")}}}) _, ok := awsEnv.EC2API.LaunchTemplates.Load(launchTemplateName) Expect(ok).To(BeTrue()) controllerutil.AddFinalizer(nodeClass, v1.TerminationFinalizer) @@ -123,7 +127,7 @@ var _ = Describe("NodeClass Termination", func() { }) It("should not delete the launch template not associated with the nodeClass", func() { launchTemplateName := aws.String(fake.LaunchTemplateName()) - awsEnv.EC2API.LaunchTemplates.Store(launchTemplateName, &ec2.LaunchTemplate{LaunchTemplateName: launchTemplateName, LaunchTemplateId: aws.String(fake.LaunchTemplateID()), Tags: []*ec2.Tag{&ec2.Tag{Key: aws.String("karpenter.k8s.aws/cluster"), Value: aws.String("test-cluster")}}}) + awsEnv.EC2API.LaunchTemplates.Store(launchTemplateName, &ec2.LaunchTemplate{LaunchTemplateName: launchTemplateName, LaunchTemplateId: aws.String(fake.LaunchTemplateID()), Tags: []*ec2.Tag{{Key: aws.String("karpenter.k8s.aws/cluster"), Value: aws.String("test-cluster")}}}) _, ok := awsEnv.EC2API.LaunchTemplates.Load(launchTemplateName) Expect(ok).To(BeTrue()) controllerutil.AddFinalizer(nodeClass, v1.TerminationFinalizer) @@ -138,9 +142,9 @@ var _ = Describe("NodeClass Termination", func() { }) It("should succeed to delete the launch template", func() { ltName1 := aws.String(fake.LaunchTemplateName()) - awsEnv.EC2API.LaunchTemplates.Store(ltName1, &ec2.LaunchTemplate{LaunchTemplateName: ltName1, LaunchTemplateId: aws.String(fake.LaunchTemplateID()), Tags: []*ec2.Tag{&ec2.Tag{Key: aws.String("karpenter.k8s.aws/cluster"), Value: aws.String("test-cluster")}, {Key: aws.String("karpenter.k8s.aws/ec2nodeclass"), Value: aws.String(nodeClass.Name)}}}) + awsEnv.EC2API.LaunchTemplates.Store(ltName1, &ec2.LaunchTemplate{LaunchTemplateName: ltName1, LaunchTemplateId: aws.String(fake.LaunchTemplateID()), Tags: []*ec2.Tag{{Key: aws.String("karpenter.k8s.aws/cluster"), Value: aws.String("test-cluster")}, {Key: aws.String("karpenter.k8s.aws/ec2nodeclass"), Value: aws.String(nodeClass.Name)}}}) ltName2 := aws.String(fake.LaunchTemplateName()) - awsEnv.EC2API.LaunchTemplates.Store(ltName2, &ec2.LaunchTemplate{LaunchTemplateName: ltName2, LaunchTemplateId: aws.String(fake.LaunchTemplateID()), Tags: []*ec2.Tag{&ec2.Tag{Key: aws.String("karpenter.k8s.aws/cluster"), Value: aws.String("test-cluster")}, {Key: aws.String("karpenter.k8s.aws/ec2nodeclass"), Value: aws.String(nodeClass.Name)}}}) + awsEnv.EC2API.LaunchTemplates.Store(ltName2, &ec2.LaunchTemplate{LaunchTemplateName: ltName2, LaunchTemplateId: aws.String(fake.LaunchTemplateID()), Tags: []*ec2.Tag{{Key: aws.String("karpenter.k8s.aws/cluster"), Value: aws.String("test-cluster")}, {Key: aws.String("karpenter.k8s.aws/ec2nodeclass"), Value: aws.String(nodeClass.Name)}}}) _, ok := awsEnv.EC2API.LaunchTemplates.Load(ltName1) Expect(ok).To(BeTrue()) _, ok = awsEnv.EC2API.LaunchTemplates.Load(ltName2) @@ -148,7 +152,6 @@ var _ = Describe("NodeClass Termination", func() { controllerutil.AddFinalizer(nodeClass, v1.TerminationFinalizer) ExpectApplied(ctx, env.Client, nodeClass) ExpectObjectReconciled(ctx, env.Client, terminationController, nodeClass) - Expect(env.Client.Delete(ctx, nodeClass)).To(Succeed()) ExpectObjectReconciled(ctx, env.Client, terminationController, nodeClass) _, ok = awsEnv.EC2API.LaunchTemplates.Load(ltName1) @@ -158,10 +161,10 @@ var _ = Describe("NodeClass Termination", func() { ExpectNotFound(ctx, env.Client, nodeClass) }) It("should succeed to delete the instance profile with no NodeClaims", func() { - awsEnv.IAMAPI.InstanceProfiles = map[string]*iam.InstanceProfile{ + awsEnv.IAMAPI.InstanceProfiles = map[string]*iamtypes.InstanceProfile{ profileName: { InstanceProfileName: aws.String(profileName), - Roles: []*iam.Role{ + Roles: []iamtypes.Role{ { RoleId: aws.String(fake.RoleID()), RoleName: aws.String(nodeClass.Spec.Role), @@ -180,7 +183,7 @@ var _ = Describe("NodeClass Termination", func() { ExpectNotFound(ctx, env.Client, nodeClass) }) It("should succeed to delete the instance profile when no roles exist with no NodeClaims", func() { - awsEnv.IAMAPI.InstanceProfiles = map[string]*iam.InstanceProfile{ + awsEnv.IAMAPI.InstanceProfiles = map[string]*iamtypes.InstanceProfile{ profileName: { InstanceProfileName: aws.String(profileName), }, @@ -189,7 +192,6 @@ var _ = Describe("NodeClass Termination", func() { ExpectApplied(ctx, env.Client, nodeClass) ExpectObjectReconciled(ctx, env.Client, terminationController, nodeClass) Expect(awsEnv.IAMAPI.InstanceProfiles).To(HaveLen(1)) - Expect(env.Client.Delete(ctx, nodeClass)).To(Succeed()) ExpectObjectReconciled(ctx, env.Client, terminationController, nodeClass) Expect(awsEnv.IAMAPI.InstanceProfiles).To(HaveLen(0)) @@ -220,10 +222,10 @@ var _ = Describe("NodeClass Termination", func() { ExpectApplied(ctx, env.Client, nc) nodeClaims = append(nodeClaims, nc) } - awsEnv.IAMAPI.InstanceProfiles = map[string]*iam.InstanceProfile{ + awsEnv.IAMAPI.InstanceProfiles = map[string]*iamtypes.InstanceProfile{ profileName: { InstanceProfileName: aws.String(profileName), - Roles: []*iam.Role{ + Roles: []iamtypes.Role{ { RoleId: aws.String(fake.RoleID()), RoleName: aws.String(nodeClass.Spec.Role), @@ -258,10 +260,10 @@ var _ = Describe("NodeClass Termination", func() { ExpectNotFound(ctx, env.Client, nodeClass) }) It("should not call the IAM API when deleting a NodeClass with an instanceProfile specified", func() { - awsEnv.IAMAPI.InstanceProfiles = map[string]*iam.InstanceProfile{ + awsEnv.IAMAPI.InstanceProfiles = map[string]*iamtypes.InstanceProfile{ profileName: { InstanceProfileName: aws.String("test-instance-profile"), - Roles: []*iam.Role{ + Roles: []iamtypes.Role{ { RoleId: aws.String(fake.RoleID()), RoleName: aws.String("fake-role"), diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go index 5267aad4672d..8e759cfd6d20 100644 --- a/pkg/errors/errors.go +++ b/pkg/errors/errors.go @@ -17,6 +17,10 @@ package errors import ( "errors" + //v2 imports + "github.com/aws/smithy-go" + + //V1 imports "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/iam" @@ -36,6 +40,9 @@ var ( "InvalidLaunchTemplateId.NotFound", sqs.ErrCodeQueueDoesNotExist, iam.ErrCodeNoSuchEntityException, + + //v2 error codes + "NoSuchEntityException", ) alreadyExistsErrorCodes = sets.New[string]( iam.ErrCodeEntityAlreadyExistsException, @@ -107,3 +114,26 @@ func IsLaunchTemplateNotFound(err error) bool { } return false } + +//V2 will become new full file when every provider is migrated + +// IsNotFound returns true if the err is an AWS error (even if it's +// wrapped) and is a known to mean "not found" (as opposed to a more +// serious or unexpected error) +func IsNotFoundV2(err error) bool { + if err == nil { + return false + } + var apiErr smithy.APIError + if errors.As(err, &apiErr) { + return notFoundErrorCodes.Has(apiErr.ErrorCode()) + } + return false +} + +func IgnoreNotFoundV2(err error) error { + if IsNotFoundV2(err) { + return nil + } + return err +} diff --git a/pkg/fake/iamapi.go b/pkg/fake/iamapi.go index 54e377b74532..d24d35b225d3 100644 --- a/pkg/fake/iamapi.go +++ b/pkg/fake/iamapi.go @@ -3,7 +3,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -11,7 +11,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - package fake import ( @@ -20,11 +19,10 @@ import ( "sync" "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/aws/aws-sdk-go/aws/request" - "github.com/aws/aws-sdk-go/service/iam" - "github.com/aws/aws-sdk-go/service/iam/iamiface" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/iam" + iamtypes "github.com/aws/aws-sdk-go-v2/service/iam/types" + "github.com/aws/smithy-go" "github.com/samber/lo" ) @@ -39,126 +37,188 @@ type IAMAPIBehavior struct { AddRoleToInstanceProfileBehavior MockedFunction[iam.AddRoleToInstanceProfileInput, iam.AddRoleToInstanceProfileOutput] TagInstanceProfileBehavior MockedFunction[iam.TagInstanceProfileInput, iam.TagInstanceProfileOutput] RemoveRoleFromInstanceProfileBehavior MockedFunction[iam.RemoveRoleFromInstanceProfileInput, iam.RemoveRoleFromInstanceProfileOutput] + UntagInstanceProfileBehavior MockedFunction[iam.UntagInstanceProfileInput, iam.UntagInstanceProfileOutput] } type IAMAPI struct { sync.Mutex - iamiface.IAMAPI + IAMClient *iam.Client IAMAPIBehavior - InstanceProfiles map[string]*iam.InstanceProfile + InstanceProfiles map[string]*iamtypes.InstanceProfile } func NewIAMAPI() *IAMAPI { - return &IAMAPI{InstanceProfiles: map[string]*iam.InstanceProfile{}} + return &IAMAPI{InstanceProfiles: map[string]*iamtypes.InstanceProfile{}} } // Reset must be called between tests otherwise tests will pollute // each other. +func toSliceOfValues[T any](value []T) []T { + values := make([]T, len(value)) + copy(values, value) + return values +} + func (s *IAMAPI) Reset() { s.GetInstanceProfileBehavior.Reset() s.CreateInstanceProfileBehavior.Reset() s.DeleteInstanceProfileBehavior.Reset() s.AddRoleToInstanceProfileBehavior.Reset() s.RemoveRoleFromInstanceProfileBehavior.Reset() - s.InstanceProfiles = map[string]*iam.InstanceProfile{} + s.InstanceProfiles = map[string]*iamtypes.InstanceProfile{} } -func (s *IAMAPI) GetInstanceProfileWithContext(_ context.Context, input *iam.GetInstanceProfileInput, _ ...request.Option) (*iam.GetInstanceProfileOutput, error) { +func (s *IAMAPI) GetInstanceProfile(ctx context.Context, input *iam.GetInstanceProfileInput, _ ...func(*iam.Options)) (*iam.GetInstanceProfileOutput, error) { return s.GetInstanceProfileBehavior.Invoke(input, func(*iam.GetInstanceProfileInput) (*iam.GetInstanceProfileOutput, error) { s.Lock() defer s.Unlock() - if i, ok := s.InstanceProfiles[aws.StringValue(input.InstanceProfileName)]; ok { + if i, ok := s.InstanceProfiles[aws.ToString(input.InstanceProfileName)]; ok { return &iam.GetInstanceProfileOutput{InstanceProfile: i}, nil } - return nil, awserr.New(iam.ErrCodeNoSuchEntityException, fmt.Sprintf("Instance Profile %s cannot be found", aws.StringValue(input.InstanceProfileName)), nil) + return nil, &smithy.GenericAPIError{ + Code: "NoSuchEntityException", + Message: fmt.Sprintf("Instance Profile %s cannot be found", + aws.ToString(input.InstanceProfileName)), + } }) } -func (s *IAMAPI) CreateInstanceProfileWithContext(_ context.Context, input *iam.CreateInstanceProfileInput, _ ...request.Option) (*iam.CreateInstanceProfileOutput, error) { +func (s *IAMAPI) CreateInstanceProfile(ctx context.Context, input *iam.CreateInstanceProfileInput, _ ...func(*iam.Options)) (*iam.CreateInstanceProfileOutput, error) { return s.CreateInstanceProfileBehavior.Invoke(input, func(output *iam.CreateInstanceProfileInput) (*iam.CreateInstanceProfileOutput, error) { s.Lock() defer s.Unlock() - if _, ok := s.InstanceProfiles[aws.StringValue(input.InstanceProfileName)]; ok { - return nil, awserr.New(iam.ErrCodeEntityAlreadyExistsException, fmt.Sprintf("Instance Profile %s already exists", aws.StringValue(input.InstanceProfileName)), nil) + if _, ok := s.InstanceProfiles[aws.ToString(input.InstanceProfileName)]; ok { + return nil, &smithy.GenericAPIError{ + Code: "EntityAlreadyExistsException", + Message: fmt.Sprintf("Instance Profile %s already exists", + aws.ToString(input.InstanceProfileName)), + } } - instanceProfile := &iam.InstanceProfile{ + instanceProfile := &iamtypes.InstanceProfile{ CreateDate: aws.Time(time.Now()), InstanceProfileId: aws.String(InstanceProfileID()), InstanceProfileName: input.InstanceProfileName, Path: input.Path, Tags: input.Tags, } - s.InstanceProfiles[aws.StringValue(input.InstanceProfileName)] = instanceProfile + s.InstanceProfiles[aws.ToString(input.InstanceProfileName)] = instanceProfile return &iam.CreateInstanceProfileOutput{InstanceProfile: instanceProfile}, nil }) } -func (s *IAMAPI) DeleteInstanceProfileWithContext(_ context.Context, input *iam.DeleteInstanceProfileInput, _ ...request.Option) (*iam.DeleteInstanceProfileOutput, error) { +func (s *IAMAPI) DeleteInstanceProfile(ctx context.Context, input *iam.DeleteInstanceProfileInput, _ ...func(*iam.Options)) (*iam.DeleteInstanceProfileOutput, error) { return s.DeleteInstanceProfileBehavior.Invoke(input, func(output *iam.DeleteInstanceProfileInput) (*iam.DeleteInstanceProfileOutput, error) { s.Lock() defer s.Unlock() - if i, ok := s.InstanceProfiles[aws.StringValue(input.InstanceProfileName)]; ok { + if i, ok := s.InstanceProfiles[aws.ToString(input.InstanceProfileName)]; ok { if len(i.Roles) > 0 { - return nil, awserr.New(iam.ErrCodeDeleteConflictException, "Cannot delete entity, must remove roles from instance profile first.", nil) + return nil, &smithy.GenericAPIError{ + Code: "DeleteConflictException", + Message: fmt.Sprintf("Instance Profile %s has roles and cannot be deleted", + aws.ToString(input.InstanceProfileName)), + } } - delete(s.InstanceProfiles, aws.StringValue(input.InstanceProfileName)) + delete(s.InstanceProfiles, aws.ToString(input.InstanceProfileName)) return &iam.DeleteInstanceProfileOutput{}, nil } - return nil, awserr.New(iam.ErrCodeNoSuchEntityException, fmt.Sprintf("Instance Profile %s cannot be found", aws.StringValue(input.InstanceProfileName)), nil) + return nil, &smithy.GenericAPIError{ + Code: "NoSuchEntityException", + Message: fmt.Sprintf("Instance Profile %s cannot be found", + aws.ToString(input.InstanceProfileName)), + } }) } -func (s *IAMAPI) TagInstanceProfileWithContext(_ context.Context, input *iam.TagInstanceProfileInput, _ ...request.Option) (*iam.TagInstanceProfileOutput, error) { +func (s *IAMAPI) TagInstanceProfile(ctx context.Context, input *iam.TagInstanceProfileInput, _ ...func(*iam.Options)) (*iam.TagInstanceProfileOutput, error) { return s.TagInstanceProfileBehavior.Invoke(input, func(output *iam.TagInstanceProfileInput) (*iam.TagInstanceProfileOutput, error) { s.Lock() defer s.Unlock() - if profile, ok := s.InstanceProfiles[aws.StringValue(input.InstanceProfileName)]; ok { - profile.Tags = lo.UniqBy(append(input.Tags, profile.Tags...), func(t *iam.Tag) string { + if profile, ok := s.InstanceProfiles[aws.ToString(input.InstanceProfileName)]; ok { + profile.Tags = lo.UniqBy(append(toSliceOfValues(input.Tags), toSliceOfValues(profile.Tags)...), func(t iamtypes.Tag) string { return lo.FromPtr(t.Key) }) return nil, nil } - return nil, awserr.New(iam.ErrCodeNoSuchEntityException, fmt.Sprintf("Instance Profile %s cannot be found", aws.StringValue(input.InstanceProfileName)), nil) + return nil, &smithy.GenericAPIError{ + Code: "NoSuchEntityException", + Message: fmt.Sprintf("Instance Profile %s cannot be found", + aws.ToString(input.InstanceProfileName)), + } }) } -func (s *IAMAPI) AddRoleToInstanceProfileWithContext(_ context.Context, input *iam.AddRoleToInstanceProfileInput, _ ...request.Option) (*iam.AddRoleToInstanceProfileOutput, error) { +func (s *IAMAPI) AddRoleToInstanceProfile(ctx context.Context, input *iam.AddRoleToInstanceProfileInput, _ ...func(*iam.Options)) (*iam.AddRoleToInstanceProfileOutput, error) { return s.AddRoleToInstanceProfileBehavior.Invoke(input, func(output *iam.AddRoleToInstanceProfileInput) (*iam.AddRoleToInstanceProfileOutput, error) { s.Lock() defer s.Unlock() - if i, ok := s.InstanceProfiles[aws.StringValue(input.InstanceProfileName)]; ok { + if i, ok := s.InstanceProfiles[aws.ToString(input.InstanceProfileName)]; ok { if len(i.Roles) > 0 { - return nil, awserr.New(iam.ErrCodeLimitExceededException, "Cannot exceed quota for InstanceSessionsPerInstanceProfile: 1", nil) + return nil, &smithy.GenericAPIError{ + Code: "LimitExceededException", + Message: fmt.Sprintf("Instance Profile %s already has a role", + aws.ToString(input.InstanceProfileName)), + } } - i.Roles = append(i.Roles, &iam.Role{RoleId: aws.String(RoleID()), RoleName: input.RoleName}) + i.Roles = append(i.Roles, iamtypes.Role{RoleId: aws.String(RoleID()), RoleName: input.RoleName}) return nil, nil } - return nil, awserr.New(iam.ErrCodeNoSuchEntityException, fmt.Sprintf("Instance Profile %s cannot be found", aws.StringValue(input.InstanceProfileName)), nil) + return nil, &smithy.GenericAPIError{ + Code: "NoSuchEntityException", + Message: fmt.Sprintf("Instance Profile %s cannot be found", + aws.ToString(input.InstanceProfileName)), + } }) } -func (s *IAMAPI) RemoveRoleFromInstanceProfileWithContext(_ context.Context, input *iam.RemoveRoleFromInstanceProfileInput, _ ...request.Option) (*iam.RemoveRoleFromInstanceProfileOutput, error) { +func (s *IAMAPI) RemoveRoleFromInstanceProfile(ctx context.Context, input *iam.RemoveRoleFromInstanceProfileInput, _ ...func(*iam.Options)) (*iam.RemoveRoleFromInstanceProfileOutput, error) { return s.RemoveRoleFromInstanceProfileBehavior.Invoke(input, func(output *iam.RemoveRoleFromInstanceProfileInput) (*iam.RemoveRoleFromInstanceProfileOutput, error) { s.Lock() defer s.Unlock() - if i, ok := s.InstanceProfiles[aws.StringValue(input.InstanceProfileName)]; ok { - newRoles := lo.Reject(i.Roles, func(r *iam.Role, _ int) bool { - return aws.StringValue(r.RoleName) == aws.StringValue(input.RoleName) + if i, ok := s.InstanceProfiles[aws.ToString(input.InstanceProfileName)]; ok { + newRoles := lo.Reject(toSliceOfValues(i.Roles), func(r iamtypes.Role, _ int) bool { + return aws.ToString(r.RoleName) == aws.ToString(input.RoleName) }) if len(i.Roles) == len(newRoles) { - return nil, awserr.New(iam.ErrCodeNoSuchEntityException, fmt.Sprintf("The role with name %s cannot be found", aws.StringValue(input.RoleName)), nil) + return nil, &smithy.GenericAPIError{ + Code: "NoSuchEntityException", + Message: fmt.Sprintf("Instance Profile %s does not have role %s", + aws.ToString(input.InstanceProfileName), aws.ToString(input.RoleName)), + } } i.Roles = newRoles return nil, nil } - return nil, awserr.New(iam.ErrCodeNoSuchEntityException, fmt.Sprintf("Instance Profile %s cannot be found", aws.StringValue(input.InstanceProfileName)), nil) + return nil, &smithy.GenericAPIError{ + Code: "NoSuchEntityException", + Message: fmt.Sprintf("Instance Profile %s cannot be found", + aws.ToString(input.InstanceProfileName)), + } + }) +} + +func (s *IAMAPI) UntagInstanceProfile(ctx context.Context, input *iam.UntagInstanceProfileInput, _ ...func(*iam.Options)) (*iam.UntagInstanceProfileOutput, error) { + return s.UntagInstanceProfileBehavior.Invoke(input, func(output *iam.UntagInstanceProfileInput) (*iam.UntagInstanceProfileOutput, error) { + s.Lock() + defer s.Unlock() + + if profile, ok := s.InstanceProfiles[aws.ToString(input.InstanceProfileName)]; ok { + profile.Tags = lo.Reject(toSliceOfValues(profile.Tags), func(t iamtypes.Tag, _ int) bool { + return lo.Contains(toSliceOfValues(input.TagKeys), lo.FromPtr(t.Key)) + }) + return nil, nil + } + return nil, &smithy.GenericAPIError{ + Code: "NoSuchEntityException", + Message: fmt.Sprintf("Instance Profile %s cannot be found", + aws.ToString(input.InstanceProfileName)), + } }) } diff --git a/pkg/operator/operator.go b/pkg/operator/operator.go index 05898b4f1b68..c88531dc1a12 100644 --- a/pkg/operator/operator.go +++ b/pkg/operator/operator.go @@ -22,6 +22,15 @@ import ( "net" "os" + //v2 + configV2 "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" + ec2V2 "github.com/aws/aws-sdk-go-v2/service/ec2" + iamV2 "github.com/aws/aws-sdk-go-v2/service/iam" + "github.com/aws/smithy-go" + prometheusv2 "github.com/jonathan-innis/aws-sdk-go-prometheus/v2" + + //v1 "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" awsclient "github.com/aws/aws-sdk-go/aws/client" @@ -33,7 +42,6 @@ import ( "github.com/aws/aws-sdk-go/service/ec2/ec2iface" "github.com/aws/aws-sdk-go/service/eks" "github.com/aws/aws-sdk-go/service/eks/eksiface" - "github.com/aws/aws-sdk-go/service/iam" "github.com/aws/aws-sdk-go/service/ssm" prometheusv1 "github.com/jonathan-innis/aws-sdk-go-prometheus/v1" "github.com/patrickmn/go-cache" @@ -73,7 +81,6 @@ type Operator struct { Session *session.Session UnavailableOfferingsCache *awscache.UnavailableOfferings - EC2API ec2iface.EC2API SubnetProvider subnet.Provider SecurityGroupProvider securitygroup.Provider InstanceProfileProvider instanceprofile.Provider @@ -88,6 +95,7 @@ type Operator struct { } func NewOperator(ctx context.Context, operator *operator.Operator) (context.Context, *Operator) { + //v1 config := &aws.Config{ STSRegionalEndpoint: endpoints.RegionalSTSEndpoint, } @@ -133,7 +141,6 @@ func NewOperator(ctx context.Context, operator *operator.Operator) (context.Cont unavailableOfferingsCache := awscache.NewUnavailableOfferings() subnetProvider := subnet.NewDefaultProvider(ec2api, cache.New(awscache.DefaultTTL, awscache.DefaultCleanupInterval), cache.New(awscache.AvailableIPAddressTTL, awscache.DefaultCleanupInterval), cache.New(awscache.AssociatePublicIPAddressTTL, awscache.DefaultCleanupInterval)) securityGroupProvider := securitygroup.NewDefaultProvider(ec2api, cache.New(awscache.DefaultTTL, awscache.DefaultCleanupInterval)) - instanceProfileProvider := instanceprofile.NewDefaultProvider(*sess.Config.Region, iam.New(sess), cache.New(awscache.InstanceProfileTTL, awscache.DefaultCleanupInterval)) pricingProvider := pricing.NewDefaultProvider( ctx, pricing.NewAPI(sess, *sess.Config.Region), @@ -174,12 +181,38 @@ func NewOperator(ctx context.Context, operator *operator.Operator) (context.Cont subnetProvider, launchTemplateProvider, ) + //v2 + cfg, err := configV2.LoadDefaultConfig(ctx, + configV2.WithRetryMaxAttempts(5), + ) + if err != nil { + panic(err) + } + prometheusv2.WithPrometheusMetrics(cfg, crmetrics.Registry) + if cfg.Region == "" { + log.FromContext(ctx).V(1).Info("retrieving region from IMDS") + metaDataClient := imds.NewFromConfig(cfg) + region, err := metaDataClient.GetRegion(ctx, nil) + if err != nil { + log.FromContext(ctx).Error(err, "failed to get region from metadata server") + os.Exit(1) + } + cfg.Region = region.Region + } + ec2apiV2 := ec2V2.NewFromConfig(cfg) + iamapi := iamV2.NewFromConfig(cfg) + //check connnectivity + if err := CheckEC2ConnectivityV2(ctx, ec2apiV2); err != nil { + log.FromContext(ctx).Error(err, "ec2 api connectivity check failed") + os.Exit(1) + } + + instanceProfileProvider := instanceprofile.NewDefaultProvider(cfg.Region, iamapi, cache.New(awscache.InstanceProfileTTL, awscache.DefaultCleanupInterval)) return ctx, &Operator{ Operator: operator, Session: sess, UnavailableOfferingsCache: unavailableOfferingsCache, - EC2API: ec2api, SubnetProvider: subnetProvider, SecurityGroupProvider: securityGroupProvider, InstanceProfileProvider: instanceProfileProvider, @@ -212,6 +245,19 @@ func CheckEC2Connectivity(ctx context.Context, api ec2iface.EC2API) error { return err } +// CheckEC2Connectivity makes a dry-run call to DescribeInstanceTypes. If it fails, we provide an early indicator that we +// are having issues connecting to the EC2 API. +func CheckEC2ConnectivityV2(ctx context.Context, api *ec2V2.Client) error { + _, err := api.DescribeInstanceTypes(ctx, &ec2V2.DescribeInstanceTypesInput{ + DryRun: aws.Bool(true), + }) + var apiErr smithy.APIError + if errors.As(err, &apiErr) && apiErr.ErrorCode() == "DryRunOperation" { + return nil + } + return err +} + func ResolveClusterEndpoint(ctx context.Context, eksAPI eksiface.EKSAPI) (string, error) { clusterEndpointFromOptions := options.FromContext(ctx).ClusterEndpoint if clusterEndpointFromOptions != "" { diff --git a/pkg/providers/instanceprofile/instanceprofile.go b/pkg/providers/instanceprofile/instanceprofile.go index 8dd66d95b7aa..37c65fac4ff7 100644 --- a/pkg/providers/instanceprofile/instanceprofile.go +++ b/pkg/providers/instanceprofile/instanceprofile.go @@ -18,15 +18,16 @@ import ( "context" "fmt" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/iam" - "github.com/aws/aws-sdk-go/service/iam/iamiface" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/iam" + iamtypes "github.com/aws/aws-sdk-go-v2/service/iam/types" "github.com/patrickmn/go-cache" "github.com/samber/lo" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" v1 "github.com/aws/karpenter-provider-aws/pkg/apis/v1" + "github.com/aws/karpenter-provider-aws/pkg/aws/sdk" awserrors "github.com/aws/karpenter-provider-aws/pkg/errors" "github.com/aws/karpenter-provider-aws/pkg/operator/options" ) @@ -46,11 +47,11 @@ type Provider interface { type DefaultProvider struct { region string - iamapi iamiface.IAMAPI + iamapi sdk.IAMAPI cache *cache.Cache } -func NewDefaultProvider(region string, iamapi iamiface.IAMAPI, cache *cache.Cache) *DefaultProvider { +func NewDefaultProvider(region string, iamapi sdk.IAMAPI, cache *cache.Cache) *DefaultProvider { return &DefaultProvider{ region: region, iamapi: iamapi, @@ -67,27 +68,27 @@ func (p *DefaultProvider) Create(ctx context.Context, m ResourceOwner) (string, return profileName, nil } // Validate if the instance profile exists and has the correct role assigned to it - var instanceProfile *iam.InstanceProfile - out, err := p.iamapi.GetInstanceProfileWithContext(ctx, &iam.GetInstanceProfileInput{InstanceProfileName: aws.String(profileName)}) + var instanceProfile *iamtypes.InstanceProfile + out, err := p.iamapi.GetInstanceProfile(ctx, &iam.GetInstanceProfileInput{InstanceProfileName: aws.String(profileName)}) if err != nil { - if !awserrors.IsNotFound(err) { + if !awserrors.IsNotFoundV2(err) { return "", fmt.Errorf("getting instance profile %q, %w", profileName, err) } - o, err := p.iamapi.CreateInstanceProfileWithContext(ctx, &iam.CreateInstanceProfileInput{ + o, err := p.iamapi.CreateInstanceProfile(ctx, &iam.CreateInstanceProfileInput{ InstanceProfileName: aws.String(profileName), - Tags: lo.MapToSlice(tags, func(k, v string) *iam.Tag { return &iam.Tag{Key: aws.String(k), Value: aws.String(v)} }), + Tags: lo.MapToSlice(tags, func(k, v string) iamtypes.Tag { return iamtypes.Tag{Key: aws.String(k), Value: aws.String(v)} }), }) if err != nil { return "", fmt.Errorf("creating instance profile %q, %w", profileName, err) } instanceProfile = o.InstanceProfile } else { - if !lo.ContainsBy(out.InstanceProfile.Tags, func(t *iam.Tag) bool { + if !lo.ContainsBy(out.InstanceProfile.Tags, func(t iamtypes.Tag) bool { return lo.FromPtr(t.Key) == v1.EKSClusterNameTagKey }) { - if _, err = p.iamapi.TagInstanceProfileWithContext(ctx, &iam.TagInstanceProfileInput{ + if _, err = p.iamapi.TagInstanceProfile(ctx, &iam.TagInstanceProfileInput{ InstanceProfileName: aws.String(profileName), - Tags: lo.MapToSlice(tags, func(k, v string) *iam.Tag { return &iam.Tag{Key: aws.String(k), Value: aws.String(v)} }), + Tags: lo.MapToSlice(tags, func(k, v string) iamtypes.Tag { return iamtypes.Tag{Key: aws.String(k), Value: aws.String(v)} }), }); err != nil { return "", fmt.Errorf("tagging instance profile %q, %w", profileName, err) } @@ -97,48 +98,48 @@ func (p *DefaultProvider) Create(ctx context.Context, m ResourceOwner) (string, // Instance profiles can only have a single role assigned to them so this profile either has 1 or 0 roles // https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_switch-role-ec2_instance-profiles.html if len(instanceProfile.Roles) == 1 { - if aws.StringValue(instanceProfile.Roles[0].RoleName) == m.InstanceProfileRole() { + if aws.ToString(instanceProfile.Roles[0].RoleName) == m.InstanceProfileRole() { return profileName, nil } - if _, err = p.iamapi.RemoveRoleFromInstanceProfileWithContext(ctx, &iam.RemoveRoleFromInstanceProfileInput{ + if _, err = p.iamapi.RemoveRoleFromInstanceProfile(ctx, &iam.RemoveRoleFromInstanceProfileInput{ InstanceProfileName: aws.String(profileName), RoleName: instanceProfile.Roles[0].RoleName, }); err != nil { - return "", fmt.Errorf("removing role %q for instance profile %q, %w", aws.StringValue(instanceProfile.Roles[0].RoleName), profileName, err) + return "", fmt.Errorf("removing role %q for instance profile %q, %w", aws.ToString(instanceProfile.Roles[0].RoleName), profileName, err) } } - if _, err = p.iamapi.AddRoleToInstanceProfileWithContext(ctx, &iam.AddRoleToInstanceProfileInput{ + if _, err = p.iamapi.AddRoleToInstanceProfile(ctx, &iam.AddRoleToInstanceProfileInput{ InstanceProfileName: aws.String(profileName), RoleName: aws.String(m.InstanceProfileRole()), }); err != nil { return "", fmt.Errorf("adding role %q to instance profile %q, %w", m.InstanceProfileRole(), profileName, err) } p.cache.SetDefault(string(m.GetUID()), nil) - return aws.StringValue(instanceProfile.InstanceProfileName), nil + return aws.ToString(instanceProfile.InstanceProfileName), nil } func (p *DefaultProvider) Delete(ctx context.Context, m ResourceOwner) error { profileName := m.InstanceProfileName(options.FromContext(ctx).ClusterName, p.region) - out, err := p.iamapi.GetInstanceProfileWithContext(ctx, &iam.GetInstanceProfileInput{ + out, err := p.iamapi.GetInstanceProfile(ctx, &iam.GetInstanceProfileInput{ InstanceProfileName: aws.String(profileName), }) if err != nil { - return awserrors.IgnoreNotFound(fmt.Errorf("getting instance profile %q, %w", profileName, err)) + return awserrors.IgnoreNotFoundV2(fmt.Errorf("getting instance profile %q, %w", profileName, err)) } // Instance profiles can only have a single role assigned to them so this profile either has 1 or 0 roles // https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_switch-role-ec2_instance-profiles.html if len(out.InstanceProfile.Roles) == 1 { - if _, err = p.iamapi.RemoveRoleFromInstanceProfileWithContext(ctx, &iam.RemoveRoleFromInstanceProfileInput{ + if _, err = p.iamapi.RemoveRoleFromInstanceProfile(ctx, &iam.RemoveRoleFromInstanceProfileInput{ InstanceProfileName: aws.String(profileName), RoleName: out.InstanceProfile.Roles[0].RoleName, }); err != nil { - return fmt.Errorf("removing role %q from instance profile %q, %w", aws.StringValue(out.InstanceProfile.Roles[0].RoleName), profileName, err) + return fmt.Errorf("removing role %q from instance profile %q, %w", aws.ToString(out.InstanceProfile.Roles[0].RoleName), profileName, err) } } - if _, err = p.iamapi.DeleteInstanceProfileWithContext(ctx, &iam.DeleteInstanceProfileInput{ + if _, err = p.iamapi.DeleteInstanceProfile(ctx, &iam.DeleteInstanceProfileInput{ InstanceProfileName: aws.String(profileName), }); err != nil { - return awserrors.IgnoreNotFound(fmt.Errorf("deleting instance profile %q, %w", profileName, err)) + return awserrors.IgnoreNotFoundV2(fmt.Errorf("deleting instance profile %q, %w", profileName, err)) } return nil } From ef81aa2da1997e21f488a01b160fa391a7ad05c4 Mon Sep 17 00:00:00 2001 From: Leah Dibble Date: Fri, 27 Sep 2024 14:06:11 -0700 Subject: [PATCH 2/5] Fix presubmit spacing issue --- go.mod | 2 +- pkg/aws/sdk/sdk.go | 3 ++- pkg/fake/iamapi.go | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 114097177773..694b140e62d3 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/aws/karpenter-provider-aws -go 1.23.1 +go 1.23.0 require ( github.com/Pallinder/go-randomdata v1.2.0 diff --git a/pkg/aws/sdk/sdk.go b/pkg/aws/sdk/sdk.go index f71b417363fb..050b99495ac9 100644 --- a/pkg/aws/sdk/sdk.go +++ b/pkg/aws/sdk/sdk.go @@ -3,7 +3,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -11,6 +11,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + package sdk import ( diff --git a/pkg/fake/iamapi.go b/pkg/fake/iamapi.go index d24d35b225d3..c83a5fcec022 100644 --- a/pkg/fake/iamapi.go +++ b/pkg/fake/iamapi.go @@ -3,7 +3,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -11,6 +11,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + package fake import ( From 2e5d09a569c43d531af33441dad46cee3bbd34a4 Mon Sep 17 00:00:00 2001 From: Leah Dibble Date: Fri, 27 Sep 2024 15:52:55 -0700 Subject: [PATCH 3/5] Removed ec2api for this PR --- go.mod | 3 --- go.sum | 6 ------ pkg/aws/sdk/sdk.go | 39 --------------------------------------- pkg/operator/operator.go | 26 ++++++++++---------------- 4 files changed, 10 insertions(+), 64 deletions(-) diff --git a/go.mod b/go.mod index 694b140e62d3..68a591f47492 100644 --- a/go.mod +++ b/go.mod @@ -11,10 +11,7 @@ require ( github.com/aws/aws-sdk-go-v2/config v1.26.6 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 github.com/aws/aws-sdk-go-v2/service/ec2 v1.177.3 - github.com/aws/aws-sdk-go-v2/service/eks v1.48.3 github.com/aws/aws-sdk-go-v2/service/iam v1.36.2 - github.com/aws/aws-sdk-go-v2/service/pricing v1.30.5 - github.com/aws/aws-sdk-go-v2/service/ssm v1.52.7 github.com/aws/karpenter-provider-aws/tools/kompat v0.0.0-20240410220356-6b868db24881 github.com/aws/smithy-go v1.21.0 github.com/awslabs/amazon-eks-ami/nodeadm v0.0.0-20240229193347-cfab22a10647 diff --git a/go.sum b/go.sum index cb69e822497d..e0b2cc66d469 100644 --- a/go.sum +++ b/go.sum @@ -26,18 +26,12 @@ github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3 h1:n3GDfwqF2tzEkXlv5cuy4iy7LpKD github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= github.com/aws/aws-sdk-go-v2/service/ec2 v1.177.3 h1:dqdCh1M8h+j8OGNUpxTs7eBPFr6lOdLpdlE6IPLLSq4= github.com/aws/aws-sdk-go-v2/service/ec2 v1.177.3/go.mod h1:TFSALWR7Xs7+KyMM87ZAYxncKFBvzEt2rpK/BJCH2ps= -github.com/aws/aws-sdk-go-v2/service/eks v1.48.3 h1:aGmoZ5b8ZUYMZ44lKRON/n2pylB6v/OmuutkX4LEL64= -github.com/aws/aws-sdk-go-v2/service/eks v1.48.3/go.mod h1:9dn8p15siUL80NCTPVNd+YvEpVTmWO+rboGx6qOMBa0= github.com/aws/aws-sdk-go-v2/service/iam v1.36.2 h1:2/kSYD8hfRU/q1HbgSzZ4PGiDmzDwtPSYgJq4yxF6bs= github.com/aws/aws-sdk-go-v2/service/iam v1.36.2/go.mod h1:HSvujsK8xeEHMIB18oMXjSfqaN9cVqpo/MtHJIksQRk= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 h1:KypMCbLPPHEmf9DgMGw51jMj77VfGPAN2Kv4cfhlfgI= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4/go.mod h1:Vz1JQXliGcQktFTN/LN6uGppAIRoLBR2bMvIMP0gOjc= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 h1:rfprUlsdzgl7ZL2KlXiUAoJnI/VxfHCvDFr2QDFj6u4= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19/go.mod h1:SCWkEdRq8/7EK60NcvvQ6NXKuTcchAD4ROAsC37VEZE= -github.com/aws/aws-sdk-go-v2/service/pricing v1.30.5 h1:Yr3T1tExevxUSylr1jb2BjGKnefy68RsPR+7gZmNGKk= -github.com/aws/aws-sdk-go-v2/service/pricing v1.30.5/go.mod h1:s25xxxgOUJZAyvM3hlt/HKIK8OQa3U+G8dyZpUFSYDU= -github.com/aws/aws-sdk-go-v2/service/ssm v1.52.7 h1:1UfdVi84wNLY2GZrp3fxldNemzVQ+uA2Nke7kZ6Nwik= -github.com/aws/aws-sdk-go-v2/service/ssm v1.52.7/go.mod h1:nUSNPaG8mv5rIu7EclHnFqZOjhreEUwRKENtKTtJ9aw= github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow= github.com/aws/aws-sdk-go-v2/service/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA= diff --git a/pkg/aws/sdk/sdk.go b/pkg/aws/sdk/sdk.go index 050b99495ac9..24d15fd253cf 100644 --- a/pkg/aws/sdk/sdk.go +++ b/pkg/aws/sdk/sdk.go @@ -17,32 +17,9 @@ package sdk import ( "context" - "github.com/aws/aws-sdk-go-v2/service/ec2" - "github.com/aws/aws-sdk-go-v2/service/eks" "github.com/aws/aws-sdk-go-v2/service/iam" - "github.com/aws/aws-sdk-go-v2/service/pricing" - "github.com/aws/aws-sdk-go-v2/service/ssm" ) -type EC2API interface { - // EC2 Methods - DescribeImages(context.Context, *ec2.DescribeImagesInput, ...func(*ec2.Options)) (*ec2.DescribeImagesOutput, error) - DescribeLaunchTemplates(context.Context, *ec2.DescribeLaunchTemplatesInput, ...func(*ec2.Options)) (*ec2.DescribeLaunchTemplatesOutput, error) - DescribeSubnets(context.Context, *ec2.DescribeSubnetsInput, ...func(*ec2.Options)) (*ec2.DescribeSubnetsOutput, error) - DescribeSecurityGroups(context.Context, *ec2.DescribeSecurityGroupsInput, ...func(*ec2.Options)) (*ec2.DescribeSecurityGroupsOutput, error) - DescribeInstanceTypes(context.Context, *ec2.DescribeInstanceTypesInput, ...func(*ec2.Options)) (*ec2.DescribeInstanceTypesOutput, error) - DescribeInstanceTypesWithContext(context.Context, *ec2.DescribeInstanceTypesInput, ...func(*ec2.Options)) (*ec2.DescribeInstanceTypesOutput, error) - DescribeInstanceTypeOfferings(context.Context, *ec2.DescribeInstanceTypeOfferingsInput, ...func(*ec2.Options)) (*ec2.DescribeInstanceTypeOfferingsOutput, error) - DescribeAvailabilityZones(context.Context, *ec2.DescribeAvailabilityZonesInput, ...func(*ec2.Options)) (*ec2.DescribeAvailabilityZonesOutput, error) - DescribeSpotPriceHistory(context.Context, *ec2.DescribeSpotPriceHistoryInput, ...func(*ec2.Options)) (*ec2.DescribeSpotPriceHistoryOutput, error) - CreateFleet(context.Context, *ec2.CreateFleetInput, ...func(*ec2.Options)) (*ec2.CreateFleetOutput, error) - TerminateInstances(context.Context, *ec2.TerminateInstancesInput, ...func(*ec2.Options)) (*ec2.TerminateInstancesOutput, error) - DescribeInstances(context.Context, *ec2.DescribeInstancesInput, ...func(*ec2.Options)) (*ec2.DescribeInstancesOutput, error) - CreateTags(context.Context, *ec2.CreateTagsInput, ...func(*ec2.Options)) (*ec2.CreateTagsOutput, error) - CreateLaunchTemplate(context.Context, *ec2.CreateLaunchTemplateInput, ...func(*ec2.Options)) (*ec2.CreateLaunchTemplateOutput, error) - DeleteLaunchTemplate(context.Context, *ec2.DeleteLaunchTemplateInput, ...func(*ec2.Options)) (*ec2.DeleteLaunchTemplateOutput, error) -} - type IAMAPI interface { // IAM Methods GetInstanceProfile(context.Context, *iam.GetInstanceProfileInput, ...func(*iam.Options)) (*iam.GetInstanceProfileOutput, error) @@ -53,19 +30,3 @@ type IAMAPI interface { RemoveRoleFromInstanceProfile(context.Context, *iam.RemoveRoleFromInstanceProfileInput, ...func(*iam.Options)) (*iam.RemoveRoleFromInstanceProfileOutput, error) UntagInstanceProfile(context.Context, *iam.UntagInstanceProfileInput, ...func(*iam.Options)) (*iam.UntagInstanceProfileOutput, error) } - -type EKSAPI interface { - // EKS Methods - DescribeCluster(context.Context, *eks.DescribeClusterInput, ...func(*eks.Options)) (*eks.DescribeClusterOutput, error) - DescribeClusterWithContext(context.Context, *eks.DescribeClusterInput, ...func(*eks.Options)) (*eks.DescribeClusterOutput, error) -} - -type PricingAPI interface { - // Pricing Methods - GetProducts(context.Context, *pricing.GetProductsInput, ...func(*pricing.Options)) (*pricing.GetProductsOutput, error) -} - -type SSMAPI interface { - // SSM Methods - GetParameter(context.Context, *ssm.GetParameterInput, ...func(*ssm.Options)) (*ssm.GetParameterOutput, error) -} diff --git a/pkg/operator/operator.go b/pkg/operator/operator.go index 820b9bd65474..9b9f24e69726 100644 --- a/pkg/operator/operator.go +++ b/pkg/operator/operator.go @@ -172,14 +172,7 @@ func NewOperator(ctx context.Context, operator *operator.Operator) (context.Cont unavailableOfferingsCache, pricingProvider, ) - instanceProvider := instance.NewDefaultProvider( - ctx, - aws.StringValue(sess.Config.Region), - ec2api, - unavailableOfferingsCache, - subnetProvider, - launchTemplateProvider, - ) + //v2 cfg, err := configV2.LoadDefaultConfig(ctx, configV2.WithRetryMaxAttempts(5), @@ -198,15 +191,16 @@ func NewOperator(ctx context.Context, operator *operator.Operator) (context.Cont } cfg.Region = region.Region } - ec2apiV2 := ec2V2.NewFromConfig(cfg) - iamapi := iamV2.NewFromConfig(cfg) - //check connnectivity - if err := CheckEC2ConnectivityV2(ctx, ec2apiV2); err != nil { - log.FromContext(ctx).Error(err, "ec2 api connectivity check failed") - os.Exit(1) - } - instanceProfileProvider := instanceprofile.NewDefaultProvider(cfg.Region, iamapi, cache.New(awscache.InstanceProfileTTL, awscache.DefaultCleanupInterval)) + instanceProfileProvider := instanceprofile.NewDefaultProvider(cfg.Region, iamV2.NewFromConfig(cfg), cache.New(awscache.InstanceProfileTTL, awscache.DefaultCleanupInterval)) + instanceProvider := instance.NewDefaultProvider( + ctx, + aws.StringValue(sess.Config.Region), + ec2api, + unavailableOfferingsCache, + subnetProvider, + launchTemplateProvider, + ) return ctx, &Operator{ Operator: operator, From e125ee5a3ce7d7893817b6f3b3b37abc52938460 Mon Sep 17 00:00:00 2001 From: Leah Dibble Date: Fri, 27 Sep 2024 16:09:32 -0700 Subject: [PATCH 4/5] removed CheckEC2Connectivity --- pkg/operator/operator.go | 31 ++++++++----------------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/pkg/operator/operator.go b/pkg/operator/operator.go index 9b9f24e69726..1583e1f96d86 100644 --- a/pkg/operator/operator.go +++ b/pkg/operator/operator.go @@ -25,9 +25,7 @@ import ( //v2 configV2 "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" - ec2V2 "github.com/aws/aws-sdk-go-v2/service/ec2" iamV2 "github.com/aws/aws-sdk-go-v2/service/iam" - "github.com/aws/smithy-go" prometheusv2 "github.com/jonathan-innis/aws-sdk-go-prometheus/v2" //v1 @@ -172,6 +170,14 @@ func NewOperator(ctx context.Context, operator *operator.Operator) (context.Cont unavailableOfferingsCache, pricingProvider, ) + instanceProvider := instance.NewDefaultProvider( + ctx, + aws.StringValue(sess.Config.Region), + ec2api, + unavailableOfferingsCache, + subnetProvider, + launchTemplateProvider, + ) //v2 cfg, err := configV2.LoadDefaultConfig(ctx, @@ -193,14 +199,6 @@ func NewOperator(ctx context.Context, operator *operator.Operator) (context.Cont } instanceProfileProvider := instanceprofile.NewDefaultProvider(cfg.Region, iamV2.NewFromConfig(cfg), cache.New(awscache.InstanceProfileTTL, awscache.DefaultCleanupInterval)) - instanceProvider := instance.NewDefaultProvider( - ctx, - aws.StringValue(sess.Config.Region), - ec2api, - unavailableOfferingsCache, - subnetProvider, - launchTemplateProvider, - ) return ctx, &Operator{ Operator: operator, @@ -238,19 +236,6 @@ func CheckEC2Connectivity(ctx context.Context, api ec2iface.EC2API) error { return err } -// CheckEC2Connectivity makes a dry-run call to DescribeInstanceTypes. If it fails, we provide an early indicator that we -// are having issues connecting to the EC2 API. -func CheckEC2ConnectivityV2(ctx context.Context, api *ec2V2.Client) error { - _, err := api.DescribeInstanceTypes(ctx, &ec2V2.DescribeInstanceTypesInput{ - DryRun: aws.Bool(true), - }) - var apiErr smithy.APIError - if errors.As(err, &apiErr) && apiErr.ErrorCode() == "DryRunOperation" { - return nil - } - return err -} - func ResolveClusterEndpoint(ctx context.Context, eksAPI eksiface.EKSAPI) (string, error) { clusterEndpointFromOptions := options.FromContext(ctx).ClusterEndpoint if clusterEndpointFromOptions != "" { From 9954d74d1c0589b0f012de6c1fa6f66632825101 Mon Sep 17 00:00:00 2001 From: Leah Dibble Date: Mon, 30 Sep 2024 09:32:59 -0700 Subject: [PATCH 5/5] Rearranged operator and added lo.Must --- pkg/operator/operator.go | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/pkg/operator/operator.go b/pkg/operator/operator.go index 1583e1f96d86..d09729557743 100644 --- a/pkg/operator/operator.go +++ b/pkg/operator/operator.go @@ -136,9 +136,24 @@ func NewOperator(ctx context.Context, operator *operator.Operator) (context.Cont log.FromContext(ctx).WithValues("kube-dns-ip", kubeDNSIP).V(1).Info("discovered kube dns") } + //v2 + cfg := lo.Must(configV2.LoadDefaultConfig(ctx)) + prometheusv2.WithPrometheusMetrics(cfg, crmetrics.Registry) + if cfg.Region == "" { + log.FromContext(ctx).V(1).Info("retrieving region from IMDS") + metaDataClient := imds.NewFromConfig(cfg) + region, err := metaDataClient.GetRegion(ctx, nil) + if err != nil { + log.FromContext(ctx).Error(err, "failed to get region from metadata server") + os.Exit(1) + } + cfg.Region = region.Region + } + unavailableOfferingsCache := awscache.NewUnavailableOfferings() subnetProvider := subnet.NewDefaultProvider(ec2api, cache.New(awscache.DefaultTTL, awscache.DefaultCleanupInterval), cache.New(awscache.AvailableIPAddressTTL, awscache.DefaultCleanupInterval), cache.New(awscache.AssociatePublicIPAddressTTL, awscache.DefaultCleanupInterval)) securityGroupProvider := securitygroup.NewDefaultProvider(ec2api, cache.New(awscache.DefaultTTL, awscache.DefaultCleanupInterval)) + instanceProfileProvider := instanceprofile.NewDefaultProvider(cfg.Region, iamV2.NewFromConfig(cfg), cache.New(awscache.InstanceProfileTTL, awscache.DefaultCleanupInterval)) pricingProvider := pricing.NewDefaultProvider( ctx, pricing.NewAPI(sess, *sess.Config.Region), @@ -179,27 +194,6 @@ func NewOperator(ctx context.Context, operator *operator.Operator) (context.Cont launchTemplateProvider, ) - //v2 - cfg, err := configV2.LoadDefaultConfig(ctx, - configV2.WithRetryMaxAttempts(5), - ) - if err != nil { - panic(err) - } - prometheusv2.WithPrometheusMetrics(cfg, crmetrics.Registry) - if cfg.Region == "" { - log.FromContext(ctx).V(1).Info("retrieving region from IMDS") - metaDataClient := imds.NewFromConfig(cfg) - region, err := metaDataClient.GetRegion(ctx, nil) - if err != nil { - log.FromContext(ctx).Error(err, "failed to get region from metadata server") - os.Exit(1) - } - cfg.Region = region.Region - } - - instanceProfileProvider := instanceprofile.NewDefaultProvider(cfg.Region, iamV2.NewFromConfig(cfg), cache.New(awscache.InstanceProfileTTL, awscache.DefaultCleanupInterval)) - return ctx, &Operator{ Operator: operator, Session: sess,