Skip to content

Commit

Permalink
Support launch templates with network configuration
Browse files Browse the repository at this point in the history
The RunInstances input needs to be cleaned from any attributes
conflicting with Network interface definition coming
from the group's LaunchTemplate

This hopefully fixes #345.
  • Loading branch information
cristim committed Jun 28, 2019
1 parent 79f41c3 commit aee6322
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 30 deletions.
32 changes: 30 additions & 2 deletions core/instance.go
Expand Up @@ -505,6 +505,27 @@ func (i *instance) convertSecurityGroups() []*string {
return groupIDs
}

func (i *instance) launchTemplateHasNetworkInterfaces(ver, id *string) bool {
res, err := i.region.services.ec2.DescribeLaunchTemplateVersions(
&ec2.DescribeLaunchTemplateVersionsInput{
Versions: []*string{ver},
LaunchTemplateId: id,
},
)

if err != nil {
logger.Println("Failed to describe launch template", *id, "version", *ver)
}

if err == nil && len(res.LaunchTemplateVersions) == 1 {
lt := res.LaunchTemplateVersions[0]
if len(lt.LaunchTemplateData.NetworkInterfaces) > 0 {
return true
}
}
return false
}

func (i *instance) createRunInstancesInput(instanceType string, price float64) *ec2.RunInstancesInput {
var retval ec2.RunInstancesInput

Expand Down Expand Up @@ -537,9 +558,16 @@ func (i *instance) createRunInstancesInput(instanceType string, price float64) *
}

if i.asg.LaunchTemplate != nil {
ver := i.asg.LaunchTemplate.Version
id := i.asg.LaunchTemplate.LaunchTemplateId

retval.LaunchTemplate = &ec2.LaunchTemplateSpecification{
LaunchTemplateId: i.asg.LaunchTemplate.LaunchTemplateId,
Version: i.asg.LaunchTemplate.Version,
LaunchTemplateId: id,
Version: ver,
}

if i.launchTemplateHasNetworkInterfaces(id, ver) {
retval.SubnetId, retval.SecurityGroupIds = nil, nil
}
}

Expand Down
160 changes: 132 additions & 28 deletions core/instance_test.go
Expand Up @@ -1537,8 +1537,22 @@ func Test_instance_createRunInstancesInput(t *testing.T) {
want *ec2.RunInstancesInput
}{
{
name: "create run instances input without launch-configuration",
name: "create run instances input with basic launch template",
inst: instance{
region: &region{
services: connections{
ec2: mockEC2{
dltverr: nil,
dltvo: &ec2.DescribeLaunchTemplateVersionsOutput{
LaunchTemplateVersions: []*ec2.LaunchTemplateVersion{
{
LaunchTemplateData: &ec2.ResponseLaunchTemplateData{},
},
},
},
},
},
},
asg: &autoScalingGroup{
name: "mygroup",
Group: &autoscaling.Group{
Expand Down Expand Up @@ -1641,8 +1655,28 @@ func Test_instance_createRunInstancesInput(t *testing.T) {
},
},
{
name: "create run instances input with simple LC",
name: "create run instances input with launch template containing advanced network configuration",
inst: instance{
region: &region{
services: connections{
ec2: mockEC2{
dltverr: nil,
dltvo: &ec2.DescribeLaunchTemplateVersionsOutput{
LaunchTemplateVersions: []*ec2.LaunchTemplateVersion{
{
LaunchTemplateData: &ec2.ResponseLaunchTemplateData{
NetworkInterfaces: []*ec2.LaunchTemplateInstanceNetworkInterfaceSpecification{
{
Description: aws.String("dummy network interface definition"),
},
},
},
},
},
},
},
},
},
asg: &autoScalingGroup{
name: "mygroup",
Group: &autoscaling.Group{
Expand All @@ -1652,6 +1686,98 @@ func Test_instance_createRunInstancesInput(t *testing.T) {
Version: aws.String("v1"),
},
},
},
Instance: &ec2.Instance{
EbsOptimized: aws.Bool(true),

IamInstanceProfile: &ec2.IamInstanceProfile{
Arn: aws.String("profile-arn"),
},

ImageId: aws.String("ami-123"),
InstanceType: aws.String("t2.medium"),
KeyName: aws.String("mykey"),

Placement: &ec2.Placement{
Affinity: aws.String("foo"),
},

SecurityGroups: []*ec2.GroupIdentifier{
{
GroupName: aws.String("foo"),
GroupId: aws.String("sg-123"),
},
{
GroupName: aws.String("bar"),
GroupId: aws.String("sg-456"),
},
},

SubnetId: aws.String("subnet-123"),
},
}, args: args{
instanceType: "t2.small",
price: 1.5,
},
want: &ec2.RunInstancesInput{

EbsOptimized: aws.Bool(true),
ImageId: aws.String("ami-123"),

InstanceMarketOptions: &ec2.InstanceMarketOptionsRequest{
MarketType: aws.String("spot"),
SpotOptions: &ec2.SpotMarketOptions{
MaxPrice: aws.String("1.5"),
},
},

InstanceType: aws.String("t2.small"),
KeyName: aws.String("mykey"),

LaunchTemplate: &ec2.LaunchTemplateSpecification{
LaunchTemplateId: aws.String("lt-id"),
Version: aws.String("v1"),
},

MaxCount: aws.Int64(1),
MinCount: aws.Int64(1),

Placement: &ec2.Placement{
Affinity: aws.String("foo"),
},

TagSpecifications: []*ec2.TagSpecification{{
ResourceType: aws.String("instance"),
Tags: []*ec2.Tag{
{
Key: aws.String("LaunchTemplateID"),
Value: aws.String("lt-id"),
},
{
Key: aws.String("LaunchTemplateVersion"),
Value: aws.String("v1"),
},
{
Key: aws.String("launched-by-autospotting"),
Value: aws.String("true"),
},
{
Key: aws.String("launched-for-asg"),
Value: aws.String("mygroup"),
},
},
},
},
},
},
{
name: "create run instances input with simple LC",
inst: instance{
asg: &autoScalingGroup{
name: "mygroup",
Group: &autoscaling.Group{
LaunchConfigurationName: aws.String("myLC"),
},
launchConfiguration: &launchConfiguration{
LaunchConfiguration: &autoscaling.LaunchConfiguration{
BlockDeviceMappings: nil,
Expand Down Expand Up @@ -1713,11 +1839,6 @@ func Test_instance_createRunInstancesInput(t *testing.T) {
InstanceType: aws.String("t2.small"),
KeyName: aws.String("mykey"),

LaunchTemplate: &ec2.LaunchTemplateSpecification{
LaunchTemplateId: aws.String("lt-id"),
Version: aws.String("v1"),
},

MaxCount: aws.Int64(1),
MinCount: aws.Int64(1),

Expand All @@ -1736,12 +1857,8 @@ func Test_instance_createRunInstancesInput(t *testing.T) {
ResourceType: aws.String("instance"),
Tags: []*ec2.Tag{
{
Key: aws.String("LaunchTemplateID"),
Value: aws.String("lt-id"),
},
{
Key: aws.String("LaunchTemplateVersion"),
Value: aws.String("v1"),
Key: aws.String("LaunchConfigurationName"),
Value: aws.String("myLC"),
},
{
Key: aws.String("launched-by-autospotting"),
Expand All @@ -1765,10 +1882,6 @@ func Test_instance_createRunInstancesInput(t *testing.T) {
name: "mygroup",
Group: &autoscaling.Group{
LaunchConfigurationName: aws.String("myLC"),
LaunchTemplate: &autoscaling.LaunchTemplateSpecification{
LaunchTemplateId: aws.String("lt-id"),
Version: aws.String("v1"),
},
},
launchConfiguration: &launchConfiguration{
LaunchConfiguration: &autoscaling.LaunchConfiguration{
Expand Down Expand Up @@ -1843,11 +1956,6 @@ func Test_instance_createRunInstancesInput(t *testing.T) {
InstanceType: aws.String("t2.small"),
KeyName: aws.String("mykey"),

LaunchTemplate: &ec2.LaunchTemplateSpecification{
LaunchTemplateId: aws.String("lt-id"),
Version: aws.String("v1"),
},

MaxCount: aws.Int64(1),
MinCount: aws.Int64(1),

Expand Down Expand Up @@ -1875,12 +1983,8 @@ func Test_instance_createRunInstancesInput(t *testing.T) {
ResourceType: aws.String("instance"),
Tags: []*ec2.Tag{
{
Key: aws.String("LaunchTemplateID"),
Value: aws.String("lt-id"),
},
{
Key: aws.String("LaunchTemplateVersion"),
Value: aws.String("v1"),
Key: aws.String("LaunchConfigurationName"),
Value: aws.String("myLC"),
},
{
Key: aws.String("launched-by-autospotting"),
Expand Down
8 changes: 8 additions & 0 deletions core/mock_test.go
Expand Up @@ -51,6 +51,10 @@ type mockEC2 struct {
// Delete Tags
dto *ec2.DeleteTagsOutput
dterr error

// DescribeLaunchTemplateVersionsOutput
dltvo *ec2.DescribeLaunchTemplateVersionsOutput
dltverr error
}

func (m mockEC2) DescribeSpotPriceHistory(in *ec2.DescribeSpotPriceHistoryInput) (*ec2.DescribeSpotPriceHistoryOutput, error) {
Expand Down Expand Up @@ -78,6 +82,10 @@ func (m mockEC2) DeleteTags(*ec2.DeleteTagsInput) (*ec2.DeleteTagsOutput, error)
return m.dto, m.dterr
}

func (m mockEC2) DescribeLaunchTemplateVersions(*ec2.DescribeLaunchTemplateVersionsInput) (*ec2.DescribeLaunchTemplateVersionsOutput, error) {
return m.dltvo, m.dltverr
}

// For testing we "convert" the SecurityGroupIDs/SecurityGroupNames by
// prefixing the original name/id with "sg-" if not present already. We
// also fill up the rest of the string to the length of a typical ID with
Expand Down

0 comments on commit aee6322

Please sign in to comment.