Skip to content

Commit af86fba

Browse files
author
Daniel Kang
authored
Merge pull request #19 from coldbrewcloud/custom-ami
Support custom ECS Container Instance images
2 parents 2cc83af + 1f3d8cc commit af86fba

7 files changed

Lines changed: 124 additions & 38 deletions

File tree

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.1.0
1+
1.2.0

aws/autoscaling/client.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ func (c *Client) CreateLaunchConfiguration(launchConfigurationName, instanceType
3030
LaunchConfigurationName: _aws.String(launchConfigurationName),
3131
SecurityGroups: _aws.StringSlice(securityGroupIDs),
3232
UserData: _aws.String(userData),
33+
InstanceMonitoring: &_autoscaling.InstanceMonitoring{Enabled: _aws.Bool(false)},
3334
}
3435

3536
if !utils.IsBlank(keyPairName) {

aws/consts.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package aws
22

33
const (
44
AWSRegionUSEast1 = "us-east-1"
5+
AWSRegionUSEast2 = "us-east-2"
56
AWSRegionUSWest1 = "us-west-1"
67
AWSRegionUSWest2 = "us-west-2"
78
AWSRegionEUWest1 = "eu-west-1"

aws/ec2/client.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,3 +408,22 @@ func (c *Client) RetrieveInstances(instanceIDs []string) ([]*_ec2.Instance, erro
408408

409409
return instances, nil
410410
}
411+
412+
func (c *Client) FindImage(ownerID, tagName string) ([]*_ec2.Image, error) {
413+
params := &_ec2.DescribeImagesInput{
414+
Owners: _aws.StringSlice([]string{ownerID}),
415+
Filters: []*_ec2.Filter{
416+
{
417+
Name: _aws.String("tag-key"),
418+
Values: _aws.StringSlice([]string{tagName}),
419+
},
420+
},
421+
}
422+
423+
res, err := c.svc.DescribeImages(params)
424+
if err != nil {
425+
return nil, err
426+
}
427+
428+
return res.Images, nil
429+
}

commands/clustercreate/aws.go

Lines changed: 66 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,33 @@ package clustercreate
33
import (
44
"encoding/base64"
55
"fmt"
6+
"strings"
67
"time"
78

9+
"github.com/aws/aws-sdk-go/service/ec2"
810
"github.com/coldbrewcloud/coldbrew-cli/aws"
11+
"github.com/coldbrewcloud/coldbrew-cli/console"
912
"github.com/coldbrewcloud/coldbrew-cli/core"
1013
"github.com/coldbrewcloud/coldbrew-cli/utils/conv"
1114
)
1215

16+
const (
17+
defaultECSContainerInstanceImageIDBaseURL = "https://s3-us-west-2.amazonaws.com/files.coldbrewcloud.com/coldbrew-cli/ecs-ci-ami/default/"
18+
defaultECSContainerInstanceImageOwnerID = "865092420289"
19+
)
20+
21+
var defaultECSContainerInstanceAmazonImageID = map[string]string{
22+
aws.AWSRegionUSEast1: "ami-1924770e",
23+
aws.AWSRegionUSEast2: "ami-bd3e64d8",
24+
aws.AWSRegionUSWest1: "ami-7f004b1f",
25+
aws.AWSRegionUSWest2: "ami-56ed4936",
26+
aws.AWSRegionEUWest1: "ami-c8337dbb",
27+
aws.AWSRegionEUCentral1: "ami-dd12ebb2",
28+
aws.AWSRegionAPNorthEast1: "ami-c8b016a9",
29+
aws.AWSRegionAPSouthEast1: "ami-6d22840e",
30+
aws.AWSRegionAPSouthEast2: "ami-73407d10",
31+
}
32+
1333
func (c *Command) getAWSInfo() (string, string, []string, error) {
1434
regionName, vpcID, err := c.globalFlags.GetAWSRegionAndVPCID()
1535
if err != nil {
@@ -28,30 +48,45 @@ func (c *Command) getAWSInfo() (string, string, []string, error) {
2848
return regionName, vpcID, subnetIDs, nil
2949
}
3050

31-
func (c *Command) getClusterImageID(region string) string {
32-
switch region {
33-
case aws.AWSRegionUSEast1:
34-
return "ami-40286957"
35-
case aws.AWSRegionUSWest1:
36-
return "ami-20fab440"
37-
case aws.AWSRegionUSWest2:
38-
return "ami-562cf236"
39-
case aws.AWSRegionEUWest1:
40-
return "ami-175f1964"
41-
case aws.AWSRegionEUCentral1:
42-
return "ami-c55ea2aa"
43-
case aws.AWSRegionAPNorthEast1:
44-
return "ami-010ed160"
45-
case aws.AWSRegionAPSouthEast1:
46-
return "ami-438b2f20"
47-
case aws.AWSRegionAPSouthEast2:
48-
return "ami-862211e5"
49-
default:
50-
return ""
51+
func (c *Command) retrieveDefaultECSContainerInstancesImageID(region string) string {
52+
defaultImages, err := c.awsClient.EC2().FindImage(defaultECSContainerInstanceImageOwnerID, core.AWSTagNameCreatedTimestamp)
53+
if err == nil {
54+
var latestImage *ec2.Image
55+
var latestImageCreationTime string
56+
57+
for _, image := range defaultImages {
58+
if conv.S(image.OwnerId) == defaultECSContainerInstanceImageOwnerID {
59+
if latestImage == nil {
60+
latestImageCreationTime = getCreationTimeFromTags(image.Tags)
61+
if latestImageCreationTime != "" {
62+
latestImage = image
63+
}
64+
} else {
65+
creationTime := getCreationTimeFromTags(image.Tags)
66+
if creationTime != "" {
67+
if strings.Compare(latestImageCreationTime, creationTime) < 0 {
68+
latestImage = image
69+
latestImageCreationTime = creationTime
70+
}
71+
}
72+
}
73+
}
74+
}
75+
76+
if latestImage != nil {
77+
return conv.S(latestImage.ImageId)
78+
}
5179
}
80+
81+
// if failed to find coldbrew-cli default image, use Amazon ECS optimized image as fallback
82+
console.Error("Failed to retrieve default image ID for ECS Container Instances. Amazon ECS Optimized AMI will be used instead.")
83+
if imageID, ok := defaultECSContainerInstanceAmazonImageID[region]; ok {
84+
return imageID
85+
}
86+
return ""
5287
}
5388

54-
func (c *Command) getInstanceUserData(ecsClusterName string) string {
89+
func (c *Command) getDefaultInstanceUserData(ecsClusterName string) string {
5590
userData := fmt.Sprintf(`#!/bin/bash
5691
echo ECS_CLUSTER=%s >> /etc/ecs/ecs.config`, ecsClusterName)
5792
return base64.StdEncoding.EncodeToString([]byte(userData))
@@ -107,3 +142,13 @@ func (c *Command) waitAutoScalingGroupDeletion(autoScalingGroupName string) erro
107142
}
108143
return nil
109144
}
145+
146+
func getCreationTimeFromTags(tags []*ec2.Tag) string {
147+
for _, tag := range tags {
148+
if conv.S(tag.Key) == core.AWSTagNameCreatedTimestamp {
149+
return conv.S(tag.Value)
150+
break
151+
}
152+
}
153+
return ""
154+
}

commands/clustercreate/command.go

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package clustercreate
22

33
import (
4+
"encoding/base64"
45
"fmt"
6+
"io/ioutil"
57
"strings"
68
"time"
79

@@ -245,12 +247,26 @@ func (c *Command) Run() error {
245247
}
246248

247249
// container instance image ID
248-
imageID := c.getClusterImageID(conv.S(c.globalFlags.AWSRegion))
249-
if imageID == "" {
250-
return console.ExitWithErrorString("No defatul instance image found")
250+
imageID := conv.S(c.commandFlags.InstanceImageID)
251+
if utils.IsBlank(imageID) {
252+
// if not provided, use coldbrew-cli default images
253+
imageID = c.retrieveDefaultECSContainerInstancesImageID(conv.S(c.globalFlags.AWSRegion))
254+
if imageID == "" {
255+
return console.ExitWithErrorString("No default instance image found")
256+
}
251257
}
252258

253-
instanceUserData := c.getInstanceUserData(ecsClusterName)
259+
// container instance userdata
260+
instanceUserData := c.getDefaultInstanceUserData(ecsClusterName)
261+
instanceUserDataFile := conv.S(c.commandFlags.InstanceUserDataFile)
262+
if !utils.IsBlank(instanceUserDataFile) {
263+
// if user provided custom user data file, use it instead
264+
fileData, err := ioutil.ReadFile(instanceUserDataFile)
265+
if err != nil {
266+
return console.ExitWithErrorString("Failed to read userdata file [%s]: %s", instanceUserDataFile, err.Error())
267+
}
268+
instanceUserData = base64.StdEncoding.EncodeToString(fileData)
269+
}
254270

255271
// NOTE: sometimes resources created (e.g. InstanceProfile) do not become available immediately.
256272
err = utils.Retry(func() (bool, error) {

commands/clustercreate/flags.go

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,25 @@ import (
66
)
77

88
type Flags struct {
9-
InstanceType *string `json:"instance_type"`
10-
InitialCapacity *uint16 `json:"initial_capacity"`
11-
NoKeyPair *bool `json:"no-keypair"`
12-
KeyPairName *string `json:"keypair_name"`
13-
InstanceProfile *string `json:"instance_profile"`
14-
ForceCreate *bool `json:"force"`
9+
InstanceType *string `json:"instance_type"`
10+
InitialCapacity *uint16 `json:"initial_capacity"`
11+
NoKeyPair *bool `json:"no-keypair"`
12+
KeyPairName *string `json:"keypair_name"`
13+
InstanceProfile *string `json:"instance_profile"`
14+
InstanceImageID *string `json:"instance_image_id"`
15+
InstanceUserDataFile *string `json:"instance_user_data_file"`
16+
ForceCreate *bool `json:"force"`
1517
}
1618

1719
func NewFlags(kc *kingpin.CmdClause) *Flags {
1820
return &Flags{
19-
InstanceType: kc.Flag("instance-type", "Container instance type").Default(core.DefaultContainerInstanceType()).String(),
20-
InitialCapacity: kc.Flag("instance-count", "Initial number of container instances").Default("1").Uint16(),
21-
NoKeyPair: kc.Flag("disable-keypair", "Do not assign EC2 keypairs").Bool(),
22-
KeyPairName: kc.Flag("key", "EC2 keypair name").Default("").String(),
23-
InstanceProfile: kc.Flag("instance-profile", "IAM instance profile name for container instances").Default("").String(),
24-
ForceCreate: kc.Flag("yes", "Create all resource with no confirmation").Short('y').Default("false").Bool(),
21+
InstanceType: kc.Flag("instance-type", "Container instance type").Default(core.DefaultContainerInstanceType()).String(),
22+
InitialCapacity: kc.Flag("instance-count", "Initial number of container instances").Default("1").Uint16(),
23+
NoKeyPair: kc.Flag("disable-keypair", "Do not assign EC2 keypairs").Bool(),
24+
KeyPairName: kc.Flag("key", "EC2 keypair name").Default("").String(),
25+
InstanceProfile: kc.Flag("instance-profile", "IAM instance profile name for container instances").Default("").String(),
26+
InstanceImageID: kc.Flag("instance-image", "EC2 Image (AMI) ID for ECS Container Instances").Default("").String(),
27+
InstanceUserDataFile: kc.Flag("instance-userdata", "File path that contains userdata for ECS Container Instances").Default("").String(),
28+
ForceCreate: kc.Flag("yes", "Create all resource with no confirmation").Short('y').Default("false").Bool(),
2529
}
2630
}

0 commit comments

Comments
 (0)