Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 69 additions & 3 deletions pkg/ami/ssm_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,19 @@ func MakeSSMParameterName(version, instanceType, imageFamily string) (string, er
return fmt.Sprintf("/aws/service/ami-windows-latest/Windows_Server-2022-English-%s-EKS_Optimized-%s/%s", windowsAmiType(imageFamily), version, fieldName), nil
case api.NodeImageFamilyBottlerocket:
return fmt.Sprintf("/aws/service/bottlerocket/aws-k8s-%s/%s/latest/%s", imageType(imageFamily, instanceType, version), instanceEC2ArchName(instanceType), fieldName), nil
case api.NodeImageFamilyUbuntuPro2204, api.NodeImageFamilyUbuntu2204, api.NodeImageFamilyUbuntu2004, api.NodeImageFamilyUbuntu1804:
// FIXME: SSM lookup for Ubuntu EKS images is supported nowadays
return "", &UnsupportedQueryError{msg: fmt.Sprintf("SSM Parameter lookups for %s AMIs is not supported yet", imageFamily)}
case api.NodeImageFamilyUbuntu1804:
return "", &UnsupportedQueryError{msg: fmt.Sprintf("SSM Parameter lookups for %s AMIs is not supported", imageFamily)}
case api.NodeImageFamilyUbuntu2004,
api.NodeImageFamilyUbuntu2204,
api.NodeImageFamilyUbuntuPro2204:
if err := validateVersionForUbuntu(version, imageFamily); err != nil {
return "", err
}
eksProduct := "eks"
if imageFamily == api.NodeImageFamilyUbuntuPro2204 {
eksProduct = "eks-pro"
}
return fmt.Sprint("/aws/service/canonical/ubuntu/", eksProduct, "/", ubuntuReleaseName(imageFamily), "/", version, "/stable/current/", ubuntuArchName(instanceType), "/hvm/ebs-gp2/ami-id"), nil
default:
return "", fmt.Errorf("unknown image family %s", imageFamily)
}
Expand Down Expand Up @@ -118,6 +128,13 @@ func instanceEC2ArchName(instanceType string) string {
return "x86_64"
}

func ubuntuArchName(instanceType string) string {
if instanceutils.IsARMInstanceType(instanceType) {
return "arm64"
}
return "amd64"
}

func imageType(imageFamily, instanceType, version string) string {
family := utils.ToKebabCase(imageFamily)
switch imageFamily {
Expand All @@ -143,3 +160,52 @@ func windowsAmiType(imageFamily string) string {
}
return "Full"
}

func ubuntuReleaseName(imageFamily string) string {
switch imageFamily {
case api.NodeImageFamilyUbuntu2004:
return "20.04"
case api.NodeImageFamilyUbuntu2204, api.NodeImageFamilyUbuntuPro2204:
return "22.04"
default:
return "18.04"
}
}

func validateVersionForUbuntu(version, imageFamily string) error {
switch imageFamily {
case api.NodeImageFamilyUbuntu2004:
var err error
supportsUbuntu := false
const minVersion = api.Version1_21
const maxVersion = api.Version1_29
supportsUbuntu, err = utils.IsMinVersion(minVersion, version)
if err != nil {
return err
}
if !supportsUbuntu {
return &UnsupportedQueryError{msg: fmt.Sprintf("%s requires EKS version greater or equal than %s and lower than %s", imageFamily, minVersion, maxVersion)}
}
supportsUbuntu, err = utils.IsMinVersion(version, maxVersion)
if err != nil {
return err
}
if !supportsUbuntu {
return &UnsupportedQueryError{msg: fmt.Sprintf("%s requires EKS version greater or equal than %s and lower than %s", imageFamily, minVersion, maxVersion)}
}
case api.NodeImageFamilyUbuntu2204, api.NodeImageFamilyUbuntuPro2204:
var err error
supportsUbuntu := false
const minVersion = api.Version1_29
supportsUbuntu, err = utils.IsMinVersion(minVersion, version)
if err != nil {
return err
}
if !supportsUbuntu {
return &UnsupportedQueryError{msg: fmt.Sprintf("%s requires EKS version greater or equal than %s", imageFamily, minVersion)}
}
default:
return &UnsupportedQueryError{msg: fmt.Sprintf("SSM Parameter lookups for %s AMIs is not supported", imageFamily)}
}
return nil
}
200 changes: 198 additions & 2 deletions pkg/ami/ssm_resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ami_test

import (
"context"
"fmt"
"strings"

"github.com/aws/aws-sdk-go-v2/aws"
Expand Down Expand Up @@ -233,17 +234,212 @@ var _ = Describe("AMI Auto Resolution", func() {

})

Context("and Ubuntu family", func() {
Context("and Ubuntu1804 family", func() {
BeforeEach(func() {
p = mockprovider.NewMockProvider()
imageFamily = "Ubuntu2004"
instanceType = "t2.medium"
imageFamily = "Ubuntu1804"
})

It("should return an error", func() {
resolver := NewSSMResolver(p.MockSSM())
resolvedAmi, err = resolver.Resolve(context.Background(), region, version, instanceType, imageFamily)

Expect(err).To(HaveOccurred())
Expect(err).To(MatchError("SSM Parameter lookups for Ubuntu1804 AMIs is not supported"))
})

})

Context("and Ubuntu2004 family", func() {
BeforeEach(func() {
p = mockprovider.NewMockProvider()
instanceType = "t2.medium"
imageFamily = "Ubuntu2004"
})

DescribeTable("should return an error",
func(version string) {
resolver := NewSSMResolver(p.MockSSM())
resolvedAmi, err = resolver.Resolve(context.Background(), region, version, instanceType, imageFamily)

Expect(err).To(HaveOccurred())
Expect(err).To(MatchError("Ubuntu2004 requires EKS version greater or equal than 1.21 and lower than 1.29"))
},
EntryDescription("When EKS version is %s"),
Entry(nil, "1.20"),
Entry(nil, "1.30"),
)

DescribeTable("should return a valid AMI",
func(version string) {
addMockGetParameter(p, fmt.Sprintf("/aws/service/canonical/ubuntu/eks/20.04/%s/stable/current/amd64/hvm/ebs-gp2/ami-id", version), expectedAmi)

resolver := NewSSMResolver(p.MockSSM())
resolvedAmi, err = resolver.Resolve(context.Background(), region, version, instanceType, imageFamily)

Expect(err).NotTo(HaveOccurred())
Expect(p.MockSSM().AssertNumberOfCalls(GinkgoT(), "GetParameter", 1)).To(BeTrue())
Expect(resolvedAmi).To(BeEquivalentTo(expectedAmi))
},
EntryDescription("When EKS version is %s"),
Entry(nil, "1.21"),
Entry(nil, "1.22"),
Entry(nil, "1.23"),
Entry(nil, "1.24"),
Entry(nil, "1.25"),
Entry(nil, "1.26"),
Entry(nil, "1.27"),
Entry(nil, "1.28"),
Entry(nil, "1.29"),
)

Context("for arm instance type", func() {
BeforeEach(func() {
instanceType = "a1.large"
})
DescribeTable("should return a valid AMI for arm64",
func(version string) {
addMockGetParameter(p, fmt.Sprintf("/aws/service/canonical/ubuntu/eks/20.04/%s/stable/current/arm64/hvm/ebs-gp2/ami-id", version), expectedAmi)

resolver := NewSSMResolver(p.MockSSM())
resolvedAmi, err = resolver.Resolve(context.Background(), region, version, instanceType, imageFamily)

Expect(err).NotTo(HaveOccurred())
Expect(p.MockSSM().AssertNumberOfCalls(GinkgoT(), "GetParameter", 1)).To(BeTrue())
Expect(resolvedAmi).To(BeEquivalentTo(expectedAmi))
},
EntryDescription("When EKS version is %s"),
Entry(nil, "1.21"),
Entry(nil, "1.22"),
Entry(nil, "1.23"),
Entry(nil, "1.24"),
Entry(nil, "1.25"),
Entry(nil, "1.26"),
Entry(nil, "1.27"),
Entry(nil, "1.28"),
Entry(nil, "1.29"),
)
})
})

Context("and Ubuntu2204 family", func() {
BeforeEach(func() {
p = mockprovider.NewMockProvider()
instanceType = "t2.medium"
imageFamily = "Ubuntu2204"
})

DescribeTable("should return an error",
func(version string) {
resolver := NewSSMResolver(p.MockSSM())
resolvedAmi, err = resolver.Resolve(context.Background(), region, version, instanceType, imageFamily)

Expect(err).To(HaveOccurred())
Expect(err).To(MatchError("Ubuntu2204 requires EKS version greater or equal than 1.29"))
},
EntryDescription("When EKS version is %s"),
Entry(nil, "1.21"),
Entry(nil, "1.28"),
)

DescribeTable("should return a valid AMI",
func(version string) {
addMockGetParameter(p, fmt.Sprintf("/aws/service/canonical/ubuntu/eks/22.04/%s/stable/current/amd64/hvm/ebs-gp2/ami-id", version), expectedAmi)

resolver := NewSSMResolver(p.MockSSM())
resolvedAmi, err = resolver.Resolve(context.Background(), region, version, instanceType, imageFamily)

Expect(err).NotTo(HaveOccurred())
Expect(p.MockSSM().AssertNumberOfCalls(GinkgoT(), "GetParameter", 1)).To(BeTrue())
Expect(resolvedAmi).To(BeEquivalentTo(expectedAmi))
},
EntryDescription("When EKS version is %s"),
Entry(nil, "1.29"),
Entry(nil, "1.30"),
Entry(nil, "1.31"),
)

Context("for arm instance type", func() {
BeforeEach(func() {
instanceType = "a1.large"
})
DescribeTable("should return a valid AMI for arm64",
func(version string) {
addMockGetParameter(p, fmt.Sprintf("/aws/service/canonical/ubuntu/eks/22.04/%s/stable/current/arm64/hvm/ebs-gp2/ami-id", version), expectedAmi)

resolver := NewSSMResolver(p.MockSSM())
resolvedAmi, err = resolver.Resolve(context.Background(), region, version, instanceType, imageFamily)

Expect(err).NotTo(HaveOccurred())
Expect(p.MockSSM().AssertNumberOfCalls(GinkgoT(), "GetParameter", 1)).To(BeTrue())
Expect(resolvedAmi).To(BeEquivalentTo(expectedAmi))
},
EntryDescription("When EKS version is %s"),
Entry(nil, "1.29"),
Entry(nil, "1.30"),
Entry(nil, "1.31"),
)
})
})

Context("and UbuntuPro2204 family", func() {
BeforeEach(func() {
p = mockprovider.NewMockProvider()
instanceType = "t2.medium"
imageFamily = "UbuntuPro2204"
})

DescribeTable("should return an error",
func(version string) {
resolver := NewSSMResolver(p.MockSSM())
resolvedAmi, err = resolver.Resolve(context.Background(), region, version, instanceType, imageFamily)

Expect(err).To(HaveOccurred())
Expect(err).To(MatchError("UbuntuPro2204 requires EKS version greater or equal than 1.29"))
},
EntryDescription("When EKS version is %s"),
Entry(nil, "1.21"),
Entry(nil, "1.28"),
)

DescribeTable("should return a valid AMI",
func(version string) {
addMockGetParameter(p, fmt.Sprintf("/aws/service/canonical/ubuntu/eks-pro/22.04/%s/stable/current/amd64/hvm/ebs-gp2/ami-id", version), expectedAmi)

resolver := NewSSMResolver(p.MockSSM())
resolvedAmi, err = resolver.Resolve(context.Background(), region, version, instanceType, imageFamily)

Expect(err).NotTo(HaveOccurred())
Expect(p.MockSSM().AssertNumberOfCalls(GinkgoT(), "GetParameter", 1)).To(BeTrue())
Expect(resolvedAmi).To(BeEquivalentTo(expectedAmi))
},
EntryDescription("When EKS version is %s"),
Entry(nil, "1.29"),
Entry(nil, "1.30"),
Entry(nil, "1.31"),
)

Context("for arm instance type", func() {
BeforeEach(func() {
instanceType = "a1.large"
})
DescribeTable("should return a valid AMI for arm64",
func(version string) {
addMockGetParameter(p, fmt.Sprintf("/aws/service/canonical/ubuntu/eks-pro/22.04/%s/stable/current/arm64/hvm/ebs-gp2/ami-id", version), expectedAmi)

resolver := NewSSMResolver(p.MockSSM())
resolvedAmi, err = resolver.Resolve(context.Background(), region, version, instanceType, imageFamily)

Expect(err).NotTo(HaveOccurred())
Expect(p.MockSSM().AssertNumberOfCalls(GinkgoT(), "GetParameter", 1)).To(BeTrue())
Expect(resolvedAmi).To(BeEquivalentTo(expectedAmi))
},
EntryDescription("When EKS version is %s"),
Entry(nil, "1.29"),
Entry(nil, "1.30"),
Entry(nil, "1.31"),
)
})
})

Expand Down
31 changes: 22 additions & 9 deletions pkg/eks/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,8 +261,8 @@ var _ = Describe("eksctl API", func() {

})

testEnsureAMI := func(matcher gomegatypes.GomegaMatcher) {
err := ResolveAMI(context.Background(), provider, "1.14", ng)
testEnsureAMI := func(matcher gomegatypes.GomegaMatcher, version string) {
err := ResolveAMI(context.Background(), provider, version, ng)
ExpectWithOffset(1, err).NotTo(HaveOccurred())
ExpectWithOffset(1, ng.AMI).To(matcher)
}
Expand All @@ -276,39 +276,52 @@ var _ = Describe("eksctl API", func() {
},
}, nil)

testEnsureAMI(Equal("ami-ssm"))
testEnsureAMI(Equal("ami-ssm"), "1.14")
})

It("should fall back to auto resolution for Ubuntu1804", func() {
ng.AMIFamily = api.NodeImageFamilyUbuntu1804
mockDescribeImages(provider, "ami-ubuntu", func(input *ec2.DescribeImagesInput) bool {
return input.Owners[0] == "099720109477"
})
testEnsureAMI(Equal("ami-ubuntu"))
testEnsureAMI(Equal("ami-ubuntu"), "1.14")
})

It("should fall back to auto resolution for Ubuntu2004", func() {
It("should fall back to auto resolution for Ubuntu2004 on 1.14", func() {
ng.AMIFamily = api.NodeImageFamilyUbuntu2004
mockDescribeImages(provider, "ami-ubuntu", func(input *ec2.DescribeImagesInput) bool {
return input.Owners[0] == "099720109477"
})
testEnsureAMI(Equal("ami-ubuntu"))
testEnsureAMI(Equal("ami-ubuntu"), "1.14")
})

It("should resolve AMI using SSM Parameter Store for Ubuntu2004 on 1.29", func() {
provider.MockSSM().On("GetParameter", mock.Anything, &ssm.GetParameterInput{
Name: aws.String("/aws/service/canonical/ubuntu/eks/20.04/1.29/stable/current/amd64/hvm/ebs-gp2/ami-id"),
}).Return(&ssm.GetParameterOutput{
Parameter: &ssmtypes.Parameter{
Value: aws.String("ami-ubuntu"),
},
}, nil)
ng.AMIFamily = api.NodeImageFamilyUbuntu2004

testEnsureAMI(Equal("ami-ubuntu"), "1.29")
})

It("should fall back to auto resolution for Ubuntu2204", func() {
ng.AMIFamily = api.NodeImageFamilyUbuntu2204
mockDescribeImages(provider, "ami-ubuntu", func(input *ec2.DescribeImagesInput) bool {
return input.Owners[0] == "099720109477"
})
testEnsureAMI(Equal("ami-ubuntu"))
testEnsureAMI(Equal("ami-ubuntu"), "1.14")
})

It("should fall back to auto resolution for UbuntuPro2204", func() {
ng.AMIFamily = api.NodeImageFamilyUbuntuPro2204
mockDescribeImages(provider, "ami-ubuntu", func(input *ec2.DescribeImagesInput) bool {
return input.Owners[0] == "099720109477"
})
testEnsureAMI(Equal("ami-ubuntu"))
testEnsureAMI(Equal("ami-ubuntu"), "1.14")
})

It("should retrieve the AMI from EC2 when AMI is auto", func() {
Expand All @@ -318,7 +331,7 @@ var _ = Describe("eksctl API", func() {
return len(input.ImageIds) == 0
})

testEnsureAMI(Equal("ami-auto"))
testEnsureAMI(Equal("ami-auto"), "1.14")
})
})

Expand Down