Skip to content

Commit d329894

Browse files
aciba90cPu1
authored andcommitted
Add auto-ssm ami resolution for ubuntu
Issue #3224
1 parent 231194c commit d329894

File tree

3 files changed

+289
-14
lines changed

3 files changed

+289
-14
lines changed

pkg/ami/ssm_resolver.go

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,19 @@ func MakeSSMParameterName(version, instanceType, imageFamily string) (string, er
7575
return fmt.Sprintf("/aws/service/ami-windows-latest/Windows_Server-2022-English-%s-EKS_Optimized-%s/%s", windowsAmiType(imageFamily), version, fieldName), nil
7676
case api.NodeImageFamilyBottlerocket:
7777
return fmt.Sprintf("/aws/service/bottlerocket/aws-k8s-%s/%s/latest/%s", imageType(imageFamily, instanceType, version), instanceEC2ArchName(instanceType), fieldName), nil
78-
case api.NodeImageFamilyUbuntuPro2204, api.NodeImageFamilyUbuntu2204, api.NodeImageFamilyUbuntu2004, api.NodeImageFamilyUbuntu1804:
79-
// FIXME: SSM lookup for Ubuntu EKS images is supported nowadays
80-
return "", &UnsupportedQueryError{msg: fmt.Sprintf("SSM Parameter lookups for %s AMIs is not supported yet", imageFamily)}
78+
case api.NodeImageFamilyUbuntu1804:
79+
return "", &UnsupportedQueryError{msg: fmt.Sprintf("SSM Parameter lookups for %s AMIs is not supported", imageFamily)}
80+
case api.NodeImageFamilyUbuntu2004,
81+
api.NodeImageFamilyUbuntu2204,
82+
api.NodeImageFamilyUbuntuPro2204:
83+
if err := validateVersionForUbuntu(version, imageFamily); err != nil {
84+
return "", err
85+
}
86+
eksProduct := "eks"
87+
if imageFamily == api.NodeImageFamilyUbuntuPro2204 {
88+
eksProduct = "eks-pro"
89+
}
90+
return fmt.Sprint("/aws/service/canonical/ubuntu/", eksProduct, "/", ubuntuReleaseName(imageFamily), "/", version, "/stable/current/", ubuntuArchName(instanceType), "/hvm/ebs-gp2/ami-id"), nil
8191
default:
8292
return "", fmt.Errorf("unknown image family %s", imageFamily)
8393
}
@@ -118,6 +128,13 @@ func instanceEC2ArchName(instanceType string) string {
118128
return "x86_64"
119129
}
120130

131+
func ubuntuArchName(instanceType string) string {
132+
if instanceutils.IsARMInstanceType(instanceType) {
133+
return "arm64"
134+
}
135+
return "amd64"
136+
}
137+
121138
func imageType(imageFamily, instanceType, version string) string {
122139
family := utils.ToKebabCase(imageFamily)
123140
switch imageFamily {
@@ -143,3 +160,52 @@ func windowsAmiType(imageFamily string) string {
143160
}
144161
return "Full"
145162
}
163+
164+
func ubuntuReleaseName(imageFamily string) string {
165+
switch imageFamily {
166+
case api.NodeImageFamilyUbuntu2004:
167+
return "20.04"
168+
case api.NodeImageFamilyUbuntu2204, api.NodeImageFamilyUbuntuPro2204:
169+
return "22.04"
170+
default:
171+
return "18.04"
172+
}
173+
}
174+
175+
func validateVersionForUbuntu(version, imageFamily string) error {
176+
switch imageFamily {
177+
case api.NodeImageFamilyUbuntu2004:
178+
var err error
179+
supportsUbuntu := false
180+
const minVersion = api.Version1_21
181+
const maxVersion = api.Version1_29
182+
supportsUbuntu, err = utils.IsMinVersion(minVersion, version)
183+
if err != nil {
184+
return err
185+
}
186+
if !supportsUbuntu {
187+
return &UnsupportedQueryError{msg: fmt.Sprintf("%s requires EKS version greater or equal than %s and lower than %s", imageFamily, minVersion, maxVersion)}
188+
}
189+
supportsUbuntu, err = utils.IsMinVersion(version, maxVersion)
190+
if err != nil {
191+
return err
192+
}
193+
if !supportsUbuntu {
194+
return &UnsupportedQueryError{msg: fmt.Sprintf("%s requires EKS version greater or equal than %s and lower than %s", imageFamily, minVersion, maxVersion)}
195+
}
196+
case api.NodeImageFamilyUbuntu2204, api.NodeImageFamilyUbuntuPro2204:
197+
var err error
198+
supportsUbuntu := false
199+
const minVersion = api.Version1_29
200+
supportsUbuntu, err = utils.IsMinVersion(minVersion, version)
201+
if err != nil {
202+
return err
203+
}
204+
if !supportsUbuntu {
205+
return &UnsupportedQueryError{msg: fmt.Sprintf("%s requires EKS version greater or equal than %s", imageFamily, minVersion)}
206+
}
207+
default:
208+
return &UnsupportedQueryError{msg: fmt.Sprintf("SSM Parameter lookups for %s AMIs is not supported", imageFamily)}
209+
}
210+
return nil
211+
}

pkg/ami/ssm_resolver_test.go

Lines changed: 198 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package ami_test
22

33
import (
44
"context"
5+
"fmt"
56
"strings"
67

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

234235
})
235236

236-
Context("and Ubuntu family", func() {
237+
Context("and Ubuntu1804 family", func() {
237238
BeforeEach(func() {
238239
p = mockprovider.NewMockProvider()
239-
imageFamily = "Ubuntu2004"
240+
instanceType = "t2.medium"
241+
imageFamily = "Ubuntu1804"
240242
})
241243

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

246248
Expect(err).To(HaveOccurred())
249+
Expect(err).To(MatchError("SSM Parameter lookups for Ubuntu1804 AMIs is not supported"))
250+
})
251+
252+
})
253+
254+
Context("and Ubuntu2004 family", func() {
255+
BeforeEach(func() {
256+
p = mockprovider.NewMockProvider()
257+
instanceType = "t2.medium"
258+
imageFamily = "Ubuntu2004"
259+
})
260+
261+
DescribeTable("should return an error",
262+
func(version string) {
263+
resolver := NewSSMResolver(p.MockSSM())
264+
resolvedAmi, err = resolver.Resolve(context.Background(), region, version, instanceType, imageFamily)
265+
266+
Expect(err).To(HaveOccurred())
267+
Expect(err).To(MatchError("Ubuntu2004 requires EKS version greater or equal than 1.21 and lower than 1.29"))
268+
},
269+
EntryDescription("When EKS version is %s"),
270+
Entry(nil, "1.20"),
271+
Entry(nil, "1.30"),
272+
)
273+
274+
DescribeTable("should return a valid AMI",
275+
func(version string) {
276+
addMockGetParameter(p, fmt.Sprintf("/aws/service/canonical/ubuntu/eks/20.04/%s/stable/current/amd64/hvm/ebs-gp2/ami-id", version), expectedAmi)
277+
278+
resolver := NewSSMResolver(p.MockSSM())
279+
resolvedAmi, err = resolver.Resolve(context.Background(), region, version, instanceType, imageFamily)
280+
281+
Expect(err).NotTo(HaveOccurred())
282+
Expect(p.MockSSM().AssertNumberOfCalls(GinkgoT(), "GetParameter", 1)).To(BeTrue())
283+
Expect(resolvedAmi).To(BeEquivalentTo(expectedAmi))
284+
},
285+
EntryDescription("When EKS version is %s"),
286+
Entry(nil, "1.21"),
287+
Entry(nil, "1.22"),
288+
Entry(nil, "1.23"),
289+
Entry(nil, "1.24"),
290+
Entry(nil, "1.25"),
291+
Entry(nil, "1.26"),
292+
Entry(nil, "1.27"),
293+
Entry(nil, "1.28"),
294+
Entry(nil, "1.29"),
295+
)
296+
297+
Context("for arm instance type", func() {
298+
BeforeEach(func() {
299+
instanceType = "a1.large"
300+
})
301+
DescribeTable("should return a valid AMI for arm64",
302+
func(version string) {
303+
addMockGetParameter(p, fmt.Sprintf("/aws/service/canonical/ubuntu/eks/20.04/%s/stable/current/arm64/hvm/ebs-gp2/ami-id", version), expectedAmi)
304+
305+
resolver := NewSSMResolver(p.MockSSM())
306+
resolvedAmi, err = resolver.Resolve(context.Background(), region, version, instanceType, imageFamily)
307+
308+
Expect(err).NotTo(HaveOccurred())
309+
Expect(p.MockSSM().AssertNumberOfCalls(GinkgoT(), "GetParameter", 1)).To(BeTrue())
310+
Expect(resolvedAmi).To(BeEquivalentTo(expectedAmi))
311+
},
312+
EntryDescription("When EKS version is %s"),
313+
Entry(nil, "1.21"),
314+
Entry(nil, "1.22"),
315+
Entry(nil, "1.23"),
316+
Entry(nil, "1.24"),
317+
Entry(nil, "1.25"),
318+
Entry(nil, "1.26"),
319+
Entry(nil, "1.27"),
320+
Entry(nil, "1.28"),
321+
Entry(nil, "1.29"),
322+
)
323+
})
324+
})
325+
326+
Context("and Ubuntu2204 family", func() {
327+
BeforeEach(func() {
328+
p = mockprovider.NewMockProvider()
329+
instanceType = "t2.medium"
330+
imageFamily = "Ubuntu2204"
331+
})
332+
333+
DescribeTable("should return an error",
334+
func(version string) {
335+
resolver := NewSSMResolver(p.MockSSM())
336+
resolvedAmi, err = resolver.Resolve(context.Background(), region, version, instanceType, imageFamily)
337+
338+
Expect(err).To(HaveOccurred())
339+
Expect(err).To(MatchError("Ubuntu2204 requires EKS version greater or equal than 1.29"))
340+
},
341+
EntryDescription("When EKS version is %s"),
342+
Entry(nil, "1.21"),
343+
Entry(nil, "1.28"),
344+
)
345+
346+
DescribeTable("should return a valid AMI",
347+
func(version string) {
348+
addMockGetParameter(p, fmt.Sprintf("/aws/service/canonical/ubuntu/eks/22.04/%s/stable/current/amd64/hvm/ebs-gp2/ami-id", version), expectedAmi)
349+
350+
resolver := NewSSMResolver(p.MockSSM())
351+
resolvedAmi, err = resolver.Resolve(context.Background(), region, version, instanceType, imageFamily)
352+
353+
Expect(err).NotTo(HaveOccurred())
354+
Expect(p.MockSSM().AssertNumberOfCalls(GinkgoT(), "GetParameter", 1)).To(BeTrue())
355+
Expect(resolvedAmi).To(BeEquivalentTo(expectedAmi))
356+
},
357+
EntryDescription("When EKS version is %s"),
358+
Entry(nil, "1.29"),
359+
Entry(nil, "1.30"),
360+
Entry(nil, "1.31"),
361+
)
362+
363+
Context("for arm instance type", func() {
364+
BeforeEach(func() {
365+
instanceType = "a1.large"
366+
})
367+
DescribeTable("should return a valid AMI for arm64",
368+
func(version string) {
369+
addMockGetParameter(p, fmt.Sprintf("/aws/service/canonical/ubuntu/eks/22.04/%s/stable/current/arm64/hvm/ebs-gp2/ami-id", version), expectedAmi)
370+
371+
resolver := NewSSMResolver(p.MockSSM())
372+
resolvedAmi, err = resolver.Resolve(context.Background(), region, version, instanceType, imageFamily)
373+
374+
Expect(err).NotTo(HaveOccurred())
375+
Expect(p.MockSSM().AssertNumberOfCalls(GinkgoT(), "GetParameter", 1)).To(BeTrue())
376+
Expect(resolvedAmi).To(BeEquivalentTo(expectedAmi))
377+
},
378+
EntryDescription("When EKS version is %s"),
379+
Entry(nil, "1.29"),
380+
Entry(nil, "1.30"),
381+
Entry(nil, "1.31"),
382+
)
383+
})
384+
})
385+
386+
Context("and UbuntuPro2204 family", func() {
387+
BeforeEach(func() {
388+
p = mockprovider.NewMockProvider()
389+
instanceType = "t2.medium"
390+
imageFamily = "UbuntuPro2204"
391+
})
392+
393+
DescribeTable("should return an error",
394+
func(version string) {
395+
resolver := NewSSMResolver(p.MockSSM())
396+
resolvedAmi, err = resolver.Resolve(context.Background(), region, version, instanceType, imageFamily)
397+
398+
Expect(err).To(HaveOccurred())
399+
Expect(err).To(MatchError("UbuntuPro2204 requires EKS version greater or equal than 1.29"))
400+
},
401+
EntryDescription("When EKS version is %s"),
402+
Entry(nil, "1.21"),
403+
Entry(nil, "1.28"),
404+
)
405+
406+
DescribeTable("should return a valid AMI",
407+
func(version string) {
408+
addMockGetParameter(p, fmt.Sprintf("/aws/service/canonical/ubuntu/eks-pro/22.04/%s/stable/current/amd64/hvm/ebs-gp2/ami-id", version), expectedAmi)
409+
410+
resolver := NewSSMResolver(p.MockSSM())
411+
resolvedAmi, err = resolver.Resolve(context.Background(), region, version, instanceType, imageFamily)
412+
413+
Expect(err).NotTo(HaveOccurred())
414+
Expect(p.MockSSM().AssertNumberOfCalls(GinkgoT(), "GetParameter", 1)).To(BeTrue())
415+
Expect(resolvedAmi).To(BeEquivalentTo(expectedAmi))
416+
},
417+
EntryDescription("When EKS version is %s"),
418+
Entry(nil, "1.29"),
419+
Entry(nil, "1.30"),
420+
Entry(nil, "1.31"),
421+
)
422+
423+
Context("for arm instance type", func() {
424+
BeforeEach(func() {
425+
instanceType = "a1.large"
426+
})
427+
DescribeTable("should return a valid AMI for arm64",
428+
func(version string) {
429+
addMockGetParameter(p, fmt.Sprintf("/aws/service/canonical/ubuntu/eks-pro/22.04/%s/stable/current/arm64/hvm/ebs-gp2/ami-id", version), expectedAmi)
430+
431+
resolver := NewSSMResolver(p.MockSSM())
432+
resolvedAmi, err = resolver.Resolve(context.Background(), region, version, instanceType, imageFamily)
433+
434+
Expect(err).NotTo(HaveOccurred())
435+
Expect(p.MockSSM().AssertNumberOfCalls(GinkgoT(), "GetParameter", 1)).To(BeTrue())
436+
Expect(resolvedAmi).To(BeEquivalentTo(expectedAmi))
437+
},
438+
EntryDescription("When EKS version is %s"),
439+
Entry(nil, "1.29"),
440+
Entry(nil, "1.30"),
441+
Entry(nil, "1.31"),
442+
)
247443
})
248444
})
249445

pkg/eks/api_test.go

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -261,8 +261,8 @@ var _ = Describe("eksctl API", func() {
261261

262262
})
263263

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

279-
testEnsureAMI(Equal("ami-ssm"))
279+
testEnsureAMI(Equal("ami-ssm"), "1.14")
280280
})
281281

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

290-
It("should fall back to auto resolution for Ubuntu2004", func() {
290+
It("should fall back to auto resolution for Ubuntu2004 on 1.14", func() {
291291
ng.AMIFamily = api.NodeImageFamilyUbuntu2004
292292
mockDescribeImages(provider, "ami-ubuntu", func(input *ec2.DescribeImagesInput) bool {
293293
return input.Owners[0] == "099720109477"
294294
})
295-
testEnsureAMI(Equal("ami-ubuntu"))
295+
testEnsureAMI(Equal("ami-ubuntu"), "1.14")
296+
})
297+
298+
It("should resolve AMI using SSM Parameter Store for Ubuntu2004 on 1.29", func() {
299+
provider.MockSSM().On("GetParameter", mock.Anything, &ssm.GetParameterInput{
300+
Name: aws.String("/aws/service/canonical/ubuntu/eks/20.04/1.29/stable/current/amd64/hvm/ebs-gp2/ami-id"),
301+
}).Return(&ssm.GetParameterOutput{
302+
Parameter: &ssmtypes.Parameter{
303+
Value: aws.String("ami-ubuntu"),
304+
},
305+
}, nil)
306+
ng.AMIFamily = api.NodeImageFamilyUbuntu2004
307+
308+
testEnsureAMI(Equal("ami-ubuntu"), "1.29")
296309
})
297310

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

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

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

321-
testEnsureAMI(Equal("ami-auto"))
334+
testEnsureAMI(Equal("ami-auto"), "1.14")
322335
})
323336
})
324337

0 commit comments

Comments
 (0)