Skip to content

Commit

Permalink
Add auto-ssm ami resolution for ubuntu
Browse files Browse the repository at this point in the history
Issue #3224
  • Loading branch information
aciba90 authored and cPu1 committed Jul 8, 2024
1 parent 231194c commit d329894
Show file tree
Hide file tree
Showing 3 changed files with 289 additions and 14 deletions.
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

0 comments on commit d329894

Please sign in to comment.