diff --git a/go.mod b/go.mod index 2d2a4fc11e0d..3f1261e50056 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,13 @@ require ( 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/iam v1.36.2 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 +41,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 daae6e9b312f..b462ef829585 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,38 @@ 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/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/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.go b/pkg/aws/sdk.go new file mode 100644 index 000000000000..24d15fd253cf --- /dev/null +++ b/pkg/aws/sdk.go @@ -0,0 +1,32 @@ +/* +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/iam" +) + +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) +} 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..dd5588232683 100644 --- a/pkg/fake/iamapi.go +++ b/pkg/fake/iamapi.go @@ -20,12 +20,13 @@ 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" + + sdk "github.com/aws/karpenter-provider-aws/pkg/aws" ) const () @@ -44,121 +45,155 @@ type IAMAPIBehavior struct { type IAMAPI struct { sync.Mutex - iamiface.IAMAPI + sdk.IAMAPI 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 (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(_ 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(_ 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(_ 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(_ 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(input.Tags, 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(_ 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(_ 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(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)), + } }) } diff --git a/pkg/operator/operator.go b/pkg/operator/operator.go index d0c9c752b091..65c7e31e10a8 100644 --- a/pkg/operator/operator.go +++ b/pkg/operator/operator.go @@ -22,6 +22,10 @@ import ( "net" "os" + configV2 "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" + iamV2 "github.com/aws/aws-sdk-go-v2/service/iam" + "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 +37,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 +76,6 @@ type Operator struct { Session *session.Session UnavailableOfferingsCache *awscache.UnavailableOfferings - EC2API ec2iface.EC2API SubnetProvider subnet.Provider SecurityGroupProvider securitygroup.Provider InstanceProfileProvider instanceprofile.Provider @@ -88,6 +90,7 @@ type Operator struct { } func NewOperator(ctx context.Context, operator *operator.Operator) (context.Context, *Operator) { + //v1 config := &aws.Config{ STSRegionalEndpoint: endpoints.RegionalSTSEndpoint, } @@ -130,10 +133,19 @@ 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 + //Once everything is migrated we will need to update prometheus metrics to v2 + cfg := lo.Must(configV2.LoadDefaultConfig(ctx, configV2.WithRetryMaxAttempts(3))) + if cfg.Region == "" { + log.FromContext(ctx).V(1).Info("retrieving region from IMDS") + metaDataClient := imds.NewFromConfig(cfg) + cfg.Region = lo.Must(metaDataClient.GetRegion(ctx, nil)).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(*sess.Config.Region, iam.New(sess), cache.New(awscache.InstanceProfileTTL, 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), @@ -176,7 +188,6 @@ func NewOperator(ctx context.Context, operator *operator.Operator) (context.Cont Operator: operator, Session: sess, UnavailableOfferingsCache: unavailableOfferingsCache, - EC2API: ec2api, SubnetProvider: subnetProvider, SecurityGroupProvider: securityGroupProvider, InstanceProfileProvider: instanceProfileProvider, diff --git a/pkg/providers/instanceprofile/instanceprofile.go b/pkg/providers/instanceprofile/instanceprofile.go index 8dd66d95b7aa..0b5df39461a9 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" + sdk "github.com/aws/karpenter-provider-aws/pkg/aws" 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 } diff --git a/website/content/en/docs/faq.md b/website/content/en/docs/faq.md index cc8aea4e829b..1dc842ccfcf4 100644 --- a/website/content/en/docs/faq.md +++ b/website/content/en/docs/faq.md @@ -29,7 +29,7 @@ Karpenter has multiple mechanisms for configuring the [operating system]({{< ref Karpenter is flexible to multi-architecture configurations using [well known labels]({{< ref "./concepts/scheduling/#supported-labels">}}). ### What RBAC access is required? -All the required RBAC rules can be found in the Helm chart template. See [clusterrole-core.yaml](https://github.com/aws/karpenter/blob/v1.0.5/charts/karpenter/templates/clusterrole-core.yaml), [clusterrole.yaml](https://github.com/aws/karpenter/blob/v1.0.5/charts/karpenter/templates/clusterrole.yaml), [rolebinding.yaml](https://github.com/aws/karpenter/blob/v1.0.5/charts/karpenter/templates/rolebinding.yaml), and [role.yaml](https://github.com/aws/karpenter/blob/v1.0.5/charts/karpenter/templates/role.yaml) files for details. +All the required RBAC rules can be found in the Helm chart template. See [clusterrole-core.yaml](https://github.com/aws/karpenter/blob/v1.0.6/charts/karpenter/templates/clusterrole-core.yaml), [clusterrole.yaml](https://github.com/aws/karpenter/blob/v1.0.6/charts/karpenter/templates/clusterrole.yaml), [rolebinding.yaml](https://github.com/aws/karpenter/blob/v1.0.6/charts/karpenter/templates/rolebinding.yaml), and [role.yaml](https://github.com/aws/karpenter/blob/v1.0.6/charts/karpenter/templates/role.yaml) files for details. ### Can I run Karpenter outside of a Kubernetes cluster? Yes, as long as the controller has network and IAM/RBAC access to the Kubernetes API and your provider API. diff --git a/website/content/en/docs/getting-started/getting-started-with-karpenter/_index.md b/website/content/en/docs/getting-started/getting-started-with-karpenter/_index.md index be240c3aa7e8..cd82fbcac7e6 100644 --- a/website/content/en/docs/getting-started/getting-started-with-karpenter/_index.md +++ b/website/content/en/docs/getting-started/getting-started-with-karpenter/_index.md @@ -48,7 +48,7 @@ After setting up the tools, set the Karpenter and Kubernetes version: ```bash export KARPENTER_NAMESPACE="kube-system" -export KARPENTER_VERSION="1.0.5" +export KARPENTER_VERSION="1.0.6" export K8S_VERSION="1.31" ``` @@ -115,13 +115,13 @@ See [Enabling Windows support](https://docs.aws.amazon.com/eks/latest/userguide/ As the OCI Helm chart is signed by [Cosign](https://github.com/sigstore/cosign) as part of the release process you can verify the chart before installing it by running the following command. ```bash -cosign verify public.ecr.aws/karpenter/karpenter:1.0.5 \ +cosign verify public.ecr.aws/karpenter/karpenter:1.0.6 \ --certificate-oidc-issuer=https://token.actions.githubusercontent.com \ --certificate-identity-regexp='https://github\.com/aws/karpenter-provider-aws/\.github/workflows/release\.yaml@.+' \ --certificate-github-workflow-repository=aws/karpenter-provider-aws \ --certificate-github-workflow-name=Release \ - --certificate-github-workflow-ref=refs/tags/v1.0.5 \ - --annotations version=1.0.5 + --certificate-github-workflow-ref=refs/tags/v1.0.6 \ + --annotations version=1.0.6 ``` {{% alert title="DNS Policy Notice" color="warning" %}} diff --git a/website/content/en/docs/upgrading/v1-migration.md b/website/content/en/docs/upgrading/v1-migration.md index e8cc091649e2..59f4a4b050d1 100644 --- a/website/content/en/docs/upgrading/v1-migration.md +++ b/website/content/en/docs/upgrading/v1-migration.md @@ -52,11 +52,11 @@ The upgrade guide will first require upgrading to your latest patch version prio The Karpenter version you are running must be between minor version `v0.33` and `v0.37`. To be able to roll back from Karpenter v1, you must rollback to on the following patch release versions for your minor version, which will include the conversion webhooks for a smooth rollback: - * v0.37.4 - * v0.36.6 - * v0.35.9 - * v0.34.10 - * v0.33.9 + * v0.37.5 + * v0.36.7 + * v0.35.10 + * v0.34.11 + * v0.33.10 3. Review for breaking changes between v0.33 and v0.37: If you are already running Karpenter v0.37.x, you can skip this step. If you are running an earlier Karpenter version, you need to review the [Upgrade Guide]({{}}) for each minor release. @@ -291,11 +291,11 @@ Keep in mind that rollback, without replacing the Karpenter nodes, will not be s Once the Karpenter CRDs are upgraded to v1, conversion webhooks are needed to help convert APIs that are stored in etcd from v1 to v1beta1. Also changes to the CRDs will need to at least include the latest version of the CRD in this case being v1. The patch versions of the v1beta1 Karpenter controller that include the conversion wehooks include: -* v0.37.4 -* v0.36.6 -* v0.35.9 -* v0.34.10 -* v0.33.9 +* v0.37.5 +* v0.36.7 +* v0.35.10 +* v0.34.11 +* v0.33.10 {{% alert title="Note" color="warning" %}} When rolling back from v1, Karpenter will not retain data that was only valid in v1 APIs. For instance, if you were upgrading from v0.33.5 to v1, updated the `NodePool.Spec.Disruption.Budgets` field and then rolled back to v0.33.6, Karpenter would not retain the `NodePool.Spec.Disruption.Budgets` field, as that was introduced in v0.34.x. If you are configuring the kubelet field, and have removed the `compatibility.karpenter.sh/v1beta1-kubelet-conversion` annotation, rollback is not supported without replacing your nodes between EC2NodeClass and NodePool. diff --git a/website/content/en/v0.36/faq.md b/website/content/en/v0.36/faq.md index 968ba65fd795..8b33850011c8 100644 --- a/website/content/en/v0.36/faq.md +++ b/website/content/en/v0.36/faq.md @@ -14,7 +14,7 @@ See [Configuring NodePools]({{< ref "./concepts/#configuring-nodepools" >}}) for AWS is the first cloud provider supported by Karpenter, although it is designed to be used with other cloud providers as well. ### Can I write my own cloud provider for Karpenter? -Yes, but there is no documentation yet for it. Start with Karpenter's GitHub [cloudprovider](https://github.com/aws/karpenter-core/tree/v0.36.6/pkg/cloudprovider) documentation to see how the AWS provider is built, but there are other sections of the code that will require changes too. +Yes, but there is no documentation yet for it. Start with Karpenter's GitHub [cloudprovider](https://github.com/aws/karpenter-core/tree/v0.36.7/pkg/cloudprovider) documentation to see how the AWS provider is built, but there are other sections of the code that will require changes too. ### What operating system nodes does Karpenter deploy? Karpenter uses the OS defined by the [AMI Family in your EC2NodeClass]({{< ref "./concepts/nodeclasses#specamifamily" >}}). @@ -26,7 +26,7 @@ Karpenter has multiple mechanisms for configuring the [operating system]({{< ref Karpenter is flexible to multi-architecture configurations using [well known labels]({{< ref "./concepts/scheduling/#supported-labels">}}). ### What RBAC access is required? -All the required RBAC rules can be found in the Helm chart template. See [clusterrole-core.yaml](https://github.com/aws/karpenter/blob/v0.36.6/charts/karpenter/templates/clusterrole-core.yaml), [clusterrole.yaml](https://github.com/aws/karpenter/blob/v0.36.6/charts/karpenter/templates/clusterrole.yaml), [rolebinding.yaml](https://github.com/aws/karpenter/blob/v0.36.6/charts/karpenter/templates/rolebinding.yaml), and [role.yaml](https://github.com/aws/karpenter/blob/v0.36.6/charts/karpenter/templates/role.yaml) files for details. +All the required RBAC rules can be found in the Helm chart template. See [clusterrole-core.yaml](https://github.com/aws/karpenter/blob/v0.36.7/charts/karpenter/templates/clusterrole-core.yaml), [clusterrole.yaml](https://github.com/aws/karpenter/blob/v0.36.7/charts/karpenter/templates/clusterrole.yaml), [rolebinding.yaml](https://github.com/aws/karpenter/blob/v0.36.7/charts/karpenter/templates/rolebinding.yaml), and [role.yaml](https://github.com/aws/karpenter/blob/v0.36.7/charts/karpenter/templates/role.yaml) files for details. ### Can I run Karpenter outside of a Kubernetes cluster? Yes, as long as the controller has network and IAM/RBAC access to the Kubernetes API and your provider API. diff --git a/website/content/en/v0.36/getting-started/getting-started-with-karpenter/_index.md b/website/content/en/v0.36/getting-started/getting-started-with-karpenter/_index.md index 5126d7baf9ed..9229e9ebbef7 100644 --- a/website/content/en/v0.36/getting-started/getting-started-with-karpenter/_index.md +++ b/website/content/en/v0.36/getting-started/getting-started-with-karpenter/_index.md @@ -45,7 +45,7 @@ After setting up the tools, set the Karpenter and Kubernetes version: ```bash export KARPENTER_NAMESPACE="kube-system" -export KARPENTER_VERSION="0.36.6" +export KARPENTER_VERSION="0.36.7" export K8S_VERSION="1.29" ``` @@ -112,13 +112,13 @@ See [Enabling Windows support](https://docs.aws.amazon.com/eks/latest/userguide/ As the OCI Helm chart is signed by [Cosign](https://github.com/sigstore/cosign) as part of the release process you can verify the chart before installing it by running the following command. ```bash -cosign verify public.ecr.aws/karpenter/karpenter:0.36.6 \ +cosign verify public.ecr.aws/karpenter/karpenter:0.36.7 \ --certificate-oidc-issuer=https://token.actions.githubusercontent.com \ --certificate-identity-regexp='https://github\.com/aws/karpenter-provider-aws/\.github/workflows/release\.yaml@.+' \ --certificate-github-workflow-repository=aws/karpenter-provider-aws \ --certificate-github-workflow-name=Release \ - --certificate-github-workflow-ref=refs/tags/v0.36.6 \ - --annotations version=0.36.6 + --certificate-github-workflow-ref=refs/tags/v0.36.7 \ + --annotations version=0.36.7 ``` {{% alert title="DNS Policy Notice" color="warning" %}} diff --git a/website/content/en/v0.36/upgrading/upgrade-guide.md b/website/content/en/v0.36/upgrading/upgrade-guide.md index edb16733526c..3b00e1f72417 100644 --- a/website/content/en/v0.36/upgrading/upgrade-guide.md +++ b/website/content/en/v0.36/upgrading/upgrade-guide.md @@ -28,9 +28,9 @@ If you get the error `invalid ownership metadata; label validation error:` while In general, you can reapply the CRDs in the `crds` directory of the Karpenter Helm chart: ```shell -kubectl apply -f https://raw.githubusercontent.com/aws/karpenter/v0.36.6/pkg/apis/crds/karpenter.sh_nodepools.yaml -kubectl apply -f https://raw.githubusercontent.com/aws/karpenter/v0.36.6/pkg/apis/crds/karpenter.sh_nodeclaims.yaml -kubectl apply -f https://raw.githubusercontent.com/aws/karpenter/v0.36.6/pkg/apis/crds/karpenter.k8s.aws_ec2nodeclasses.yaml +kubectl apply -f https://raw.githubusercontent.com/aws/karpenter/v0.36.7/pkg/apis/crds/karpenter.sh_nodepools.yaml +kubectl apply -f https://raw.githubusercontent.com/aws/karpenter/v0.36.7/pkg/apis/crds/karpenter.sh_nodeclaims.yaml +kubectl apply -f https://raw.githubusercontent.com/aws/karpenter/v0.36.7/pkg/apis/crds/karpenter.k8s.aws_ec2nodeclasses.yaml ```