Use LaunchTemplate versions instead of timestamped LaunchTemplates

This commit is contained in:
Ciprian Hacman 2020-11-02 09:25:51 +02:00
parent ecea47790b
commit 565adceab9
8 changed files with 189 additions and 247 deletions

View File

@ -18,6 +18,7 @@ package mockautoscaling
import (
"fmt"
"hash/crc64"
"time"
"github.com/aws/aws-sdk-go/aws"
@ -30,7 +31,7 @@ func (m *MockAutoscaling) AttachInstances(input *autoscaling.AttachInstancesInpu
m.mutex.Lock()
defer m.mutex.Unlock()
klog.V(2).Infof("AttachInstances %v", input)
klog.V(2).Infof("Mock AttachInstances %v", input)
g := m.Groups[aws.StringValue(input.AutoScalingGroupName)]
if g == nil {
@ -48,7 +49,8 @@ func (m *MockAutoscaling) CreateAutoScalingGroup(input *autoscaling.CreateAutoSc
m.mutex.Lock()
defer m.mutex.Unlock()
klog.V(2).Infof("CreateAutoScalingGroup %v", input)
klog.V(2).Infof("Mock CreateAutoScalingGroup %v", input)
createdTime := time.Now().UTC()
g := &autoscaling.Group{
@ -75,6 +77,11 @@ func (m *MockAutoscaling) CreateAutoScalingGroup(input *autoscaling.CreateAutoSc
VPCZoneIdentifier: input.VPCZoneIdentifier,
}
if input.LaunchTemplate != nil {
launchTemplateCrc := crc64.Checksum([]byte(aws.StringValue(input.LaunchTemplate.LaunchTemplateName)), crc64.MakeTable(crc64.ECMA))
g.LaunchTemplate.LaunchTemplateId = aws.String(fmt.Sprintf("lt-%x", launchTemplateCrc))
}
for _, tag := range input.Tags {
g.Tags = append(g.Tags, &autoscaling.TagDescription{
Key: tag.Key,
@ -97,7 +104,7 @@ func (m *MockAutoscaling) EnableMetricsCollection(request *autoscaling.EnableMet
m.mutex.Lock()
defer m.mutex.Unlock()
klog.Infof("EnableMetricsCollection: %v", request)
klog.V(2).Infof("Mock EnableMetricsCollection: %v", request)
g := m.Groups[*request.AutoScalingGroupName]
if g == nil {
@ -129,7 +136,7 @@ func (m *MockAutoscaling) SuspendProcesses(input *autoscaling.ScalingProcessQuer
m.mutex.Lock()
defer m.mutex.Unlock()
klog.Infof("EnableMetricsCollection: %v", input)
klog.V(2).Infof("Mock SuspendProcesses: %v", input)
g := m.Groups[*input.AutoScalingGroupName]
if g == nil {
@ -157,6 +164,8 @@ func (m *MockAutoscaling) DescribeAutoScalingGroups(input *autoscaling.DescribeA
m.mutex.Lock()
defer m.mutex.Unlock()
klog.V(2).Infof("Mock DescribeAutoScalingGroups: %v", input)
groups := []*autoscaling.Group{}
for _, group := range m.Groups {
match := false
@ -236,7 +245,7 @@ func (m *MockAutoscaling) DeleteAutoScalingGroup(request *autoscaling.DeleteAuto
m.mutex.Lock()
defer m.mutex.Unlock()
klog.Infof("DeleteAutoScalingGroup: %v", request)
klog.V(2).Infof("Mock DeleteAutoScalingGroup: %v", request)
id := aws.StringValue(request.AutoScalingGroupName)
o := m.Groups[id]

View File

@ -55,8 +55,7 @@ type MockEC2 struct {
InternetGateways map[string]*ec2.InternetGateway
launchTemplateNumber int
LaunchTemplates map[string]*launchTemplateInfo
LaunchTemplates map[string]*launchTemplateInfo
NatGateways map[string]*ec2.NatGateway

View File

@ -18,9 +18,11 @@ package mockec2
import (
"fmt"
"hash/crc64"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"k8s.io/klog/v2"
)
type launchTemplateInfo struct {
@ -33,15 +35,18 @@ func (m *MockEC2) DescribeLaunchTemplates(request *ec2.DescribeLaunchTemplatesIn
m.mutex.Lock()
defer m.mutex.Unlock()
klog.V(2).Infof("Mock DescribeLaunchTemplates: %v", request)
o := &ec2.DescribeLaunchTemplatesOutput{}
if m.LaunchTemplates == nil {
return o, nil
}
for _, ltInfo := range m.LaunchTemplates {
for id, ltInfo := range m.LaunchTemplates {
o.LaunchTemplates = append(o.LaunchTemplates, &ec2.LaunchTemplate{
LaunchTemplateName: ltInfo.name,
LaunchTemplateId: aws.String(id),
})
}
@ -53,6 +58,8 @@ func (m *MockEC2) DescribeLaunchTemplateVersions(request *ec2.DescribeLaunchTemp
m.mutex.Lock()
defer m.mutex.Unlock()
klog.V(2).Infof("Mock DescribeLaunchTemplateVersions: %v", request)
o := &ec2.DescribeLaunchTemplateVersionsOutput{}
if m.LaunchTemplates == nil {
@ -78,15 +85,16 @@ func (m *MockEC2) CreateLaunchTemplate(request *ec2.CreateLaunchTemplateInput) (
m.mutex.Lock()
defer m.mutex.Unlock()
m.launchTemplateNumber++
n := m.launchTemplateNumber
id := fmt.Sprintf("lt-%d", n)
klog.V(2).Infof("Mock CreateLaunchTemplate: %v", request)
crc := crc64.Checksum([]byte(aws.StringValue(request.LaunchTemplateName)), crc64.MakeTable(crc64.ECMA))
id := fmt.Sprintf("lt-%x", crc)
if m.LaunchTemplates == nil {
m.LaunchTemplates = make(map[string]*launchTemplateInfo)
}
if m.LaunchTemplates[id] != nil {
return nil, fmt.Errorf("duplicate LaunchTemplateName %s", id)
return nil, fmt.Errorf("duplicate LaunchTemplateId %s", id)
}
resp := &ec2.ResponseLaunchTemplateData{
DisableApiTermination: request.LaunchTemplateData.DisableApiTermination,
@ -190,13 +198,15 @@ func (m *MockEC2) DeleteLaunchTemplate(request *ec2.DeleteLaunchTemplateInput) (
m.mutex.Lock()
defer m.mutex.Unlock()
klog.V(2).Infof("Mock DeleteLaunchTemplate: %v", request)
o := &ec2.DeleteLaunchTemplateOutput{}
if m.LaunchTemplates == nil {
return o, nil
}
for id, lt := range m.LaunchTemplates {
if aws.StringValue(lt.name) == aws.StringValue(request.LaunchTemplateName) {
for id := range m.LaunchTemplates {
if id == aws.StringValue(request.LaunchTemplateId) {
delete(m.LaunchTemplates, id)
}
}

View File

@ -145,7 +145,7 @@ func ListResourcesAWS(cloud awsup.AWSCloud, clusterName string) (map[string]*res
if err != nil {
return nil, err
}
lts, err := FindAutoScalingLaunchTemplateConfigurations(cloud, securityGroups)
lts, err := FindAutoScalingLaunchTemplates(cloud, clusterName, securityGroups)
if err != nil {
return nil, err
}
@ -1179,47 +1179,34 @@ func ListAutoScalingGroups(cloud fi.Cloud, clusterName string) ([]*resources.Res
return resourceTrackers, nil
}
// FindAutoScalingLaunchTemplateConfigurations finds any launch configurations which reference the security groups
func FindAutoScalingLaunchTemplateConfigurations(cloud fi.Cloud, securityGroups sets.String) ([]*resources.Resource, error) {
var list []*resources.Resource
// FindAutoScalingLaunchTemplates finds any launch configurations which reference the security groups
func FindAutoScalingLaunchTemplates(cloud fi.Cloud, clusterName string, securityGroups sets.String) ([]*resources.Resource, error) {
c, ok := cloud.(awsup.AWSCloud)
if !ok {
return nil, errors.New("expected a aws cloud provider")
}
klog.V(2).Infof("Finding all Autoscaling LaunchTemplates associated to security groups")
resp, err := c.EC2().DescribeLaunchTemplates(&ec2.DescribeLaunchTemplatesInput{MaxResults: fi.Int64(100)})
output, err := c.EC2().DescribeLaunchTemplates(&ec2.DescribeLaunchTemplatesInput{
Filters: []*ec2.Filter{
{
Name: aws.String("tag:KubernetesCluster"),
Values: []*string{aws.String(clusterName)},
},
},
})
if err != nil {
return list, nil
return nil, nil
}
for _, x := range resp.LaunchTemplates {
// @step: grab the actual launch template
req, err := c.EC2().DescribeLaunchTemplateVersions(&ec2.DescribeLaunchTemplateVersionsInput{
LaunchTemplateName: x.LaunchTemplateName,
var list []*resources.Resource
for _, lt := range output.LaunchTemplates {
list = append(list, &resources.Resource{
Name: aws.StringValue(lt.LaunchTemplateName),
ID: aws.StringValue(lt.LaunchTemplateId),
Type: TypeAutoscalingLaunchConfig,
Deleter: DeleteAutoScalingGroupLaunchTemplate,
})
if err != nil {
return list, err
}
for _, j := range req.LaunchTemplateVersions {
// @check if the security group references the security group above
var s []*string
for _, ni := range j.LaunchTemplateData.NetworkInterfaces {
s = append(s, ni.Groups...)
}
s = append(s, j.LaunchTemplateData.SecurityGroupIds...)
for _, y := range s {
if securityGroups.Has(fi.StringValue(y)) {
list = append(list, &resources.Resource{
Name: aws.StringValue(x.LaunchTemplateName),
ID: aws.StringValue(x.LaunchTemplateName),
Type: TypeAutoscalingLaunchConfig,
Deleter: DeleteAutoScalingGroupLaunchTemplate,
})
}
}
}
}
return list, nil
@ -1377,7 +1364,7 @@ func DeleteAutoScalingGroupLaunchTemplate(cloud fi.Cloud, r *resources.Resource)
klog.V(2).Infof("Deleting EC2 LaunchTemplate %q", r.ID)
if _, err := c.EC2().DeleteLaunchTemplate(&ec2.DeleteLaunchTemplateInput{
LaunchTemplateName: fi.String(r.ID),
LaunchTemplateId: fi.String(r.ID),
}); err != nil {
return fmt.Errorf("error deleting ec2 LaunchTemplate %q: %v", r.ID, err)
}

View File

@ -20,19 +20,16 @@ import (
"fmt"
"reflect"
"sort"
"strconv"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/autoscaling"
"k8s.io/klog/v2"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"k8s.io/kops/upup/pkg/fi/cloudup/cloudformation"
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
"k8s.io/kops/util/pkg/maps"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/autoscaling"
"github.com/aws/aws-sdk-go/service/ec2"
"k8s.io/klog/v2"
)
// CloudTagInstanceGroupRolePrefix is a cloud tag that defines the instance role
@ -202,7 +199,10 @@ func (e *AutoscalingGroup) Find(c *fi.Context) (*AutoscalingGroup, error) {
actual.LaunchConfiguration = &LaunchConfiguration{ID: g.LaunchConfigurationName}
}
if g.LaunchTemplate != nil {
actual.LaunchTemplate = &LaunchTemplate{ID: g.LaunchTemplate.LaunchTemplateName}
actual.LaunchTemplate = &LaunchTemplate{
Name: g.LaunchTemplate.LaunchTemplateName,
ID: g.LaunchTemplate.LaunchTemplateId,
}
}
if g.MixedInstancesPolicy != nil {
@ -219,7 +219,10 @@ func (e *AutoscalingGroup) Find(c *fi.Context) (*AutoscalingGroup, error) {
if g.MixedInstancesPolicy.LaunchTemplate != nil {
if g.MixedInstancesPolicy.LaunchTemplate.LaunchTemplateSpecification != nil {
actual.LaunchTemplate = &LaunchTemplate{ID: g.MixedInstancesPolicy.LaunchTemplate.LaunchTemplateSpecification.LaunchTemplateName}
actual.LaunchTemplate = &LaunchTemplate{
Name: g.MixedInstancesPolicy.LaunchTemplate.LaunchTemplateSpecification.LaunchTemplateName,
ID: g.MixedInstancesPolicy.LaunchTemplate.LaunchTemplateSpecification.LaunchTemplateId,
}
}
for _, n := range g.MixedInstancesPolicy.LaunchTemplate.Overrides {
@ -363,8 +366,8 @@ func (v *AutoscalingGroup) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *Autos
},
LaunchTemplate: &autoscaling.LaunchTemplate{
LaunchTemplateSpecification: &autoscaling.LaunchTemplateSpecification{
LaunchTemplateName: e.LaunchTemplate.ID,
Version: aws.String("1"),
LaunchTemplateName: e.LaunchTemplate.Name,
Version: aws.String("$Latest"),
},
},
}
@ -376,11 +379,11 @@ func (v *AutoscalingGroup) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *Autos
}
} else if e.LaunchTemplate != nil {
request.LaunchTemplate = &autoscaling.LaunchTemplateSpecification{
LaunchTemplateName: e.LaunchTemplate.ID,
Version: aws.String("1"),
LaunchTemplateName: e.LaunchTemplate.Name,
Version: aws.String("$Latest"),
}
} else {
return fmt.Errorf("could not find one of launch configuration, mixed instances policy, or launch template")
return fmt.Errorf("could not find one of launch template, mixed instances policy, or launch configuration")
}
// @step: attempt to create the autoscaling group for us
@ -441,23 +444,10 @@ func (v *AutoscalingGroup) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *Autos
return req.MixedInstancesPolicy
}
launchTemplateVersion := "1"
if e.LaunchTemplate != nil {
dltRequest := &ec2.DescribeLaunchTemplatesInput{
LaunchTemplateNames: []*string{e.LaunchTemplate.ID},
}
dltResponse, err := t.Cloud.EC2().DescribeLaunchTemplates(dltRequest)
if err != nil {
klog.Warningf("could not find existing LaunchTemplate: %v", err)
} else {
launchTemplateVersion = strconv.FormatInt(*dltResponse.LaunchTemplates[0].LatestVersionNumber, 10)
}
}
if changes.LaunchTemplate != nil {
spec := &autoscaling.LaunchTemplateSpecification{
LaunchTemplateName: changes.LaunchTemplate.ID,
Version: &launchTemplateVersion,
LaunchTemplateName: changes.LaunchTemplate.Name,
Version: aws.String("$Latest"),
}
if e.UseMixedInstancesPolicy() {
setup(request).LaunchTemplate = &autoscaling.LaunchTemplate{LaunchTemplateSpecification: spec}
@ -491,8 +481,8 @@ func (v *AutoscalingGroup) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *Autos
if setup(request).LaunchTemplate == nil {
setup(request).LaunchTemplate = &autoscaling.LaunchTemplate{
LaunchTemplateSpecification: &autoscaling.LaunchTemplateSpecification{
LaunchTemplateName: e.LaunchTemplate.ID,
Version: &launchTemplateVersion,
LaunchTemplateName: changes.LaunchTemplate.Name,
Version: aws.String("$Latest"),
},
}
}

View File

@ -19,17 +19,18 @@ package awstasks
import (
"fmt"
"sort"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"strings"
"github.com/aws/aws-sdk-go/aws"
"k8s.io/klog/v2"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
)
// LaunchTemplate defines the specification for a launch template.
// +kops:fitask
type LaunchTemplate struct {
// ID is the launch configuration name
ID *string
// Name is the name of the configuration
Name *string
// Lifecycle is the resource lifecycle
@ -41,10 +42,11 @@ type LaunchTemplate struct {
BlockDeviceMappings []*BlockDeviceMapping
// IAMInstanceProfile is the IAM profile to assign to the nodes
IAMInstanceProfile *IAMInstanceProfile
// ID is the launch configuration name
ID *string
// ImageID is the AMI to use for the instances
ImageID *string
// InstanceInterruptionBehavior defines if a spot instance should be terminated, hibernated,
// or stopped after interruption
InstanceInterruptionBehavior *string
// InstanceMonitoring indicates if monitoring is enabled
InstanceMonitoring *bool
// InstanceType is the type of instance we are using
@ -73,9 +75,6 @@ type LaunchTemplate struct {
Tenancy *string
// UserData is the user data configuration
UserData *fi.ResourceHolder
// InstanceInterruptionBehavior defines if a spot instance should be terminated, hibernated,
// or stopped after interruption
InstanceInterruptionBehavior *string
}
var (
@ -89,11 +88,6 @@ func (t *LaunchTemplate) CompareWithID() *string {
return t.ID
}
// LaunchTemplateName returns the lanuch template name
func (t *LaunchTemplate) LaunchTemplateName() string {
return fmt.Sprintf("%s-%s", fi.StringValue(t.Name), fi.BuildTimestampString())
}
// buildRootDevice is responsible for retrieving a boot device mapping from the image name
func (t *LaunchTemplate) buildRootDevice(cloud awsup.AWSCloud) (map[string]*BlockDeviceMapping, error) {
image := fi.StringValue(t.ImageID)
@ -154,22 +148,17 @@ func (t *LaunchTemplate) CheckChanges(a, e, changes *LaunchTemplate) error {
func (t *LaunchTemplate) FindDeletions(c *fi.Context) ([]fi.Deletion, error) {
var removals []fi.Deletion
configurations, err := t.findLaunchTemplates(c)
configurations, err := t.findAllLaunchTemplates(c)
if err != nil {
return nil, err
}
if len(configurations) <= RetainLaunchConfigurationCount() {
return nil, nil
}
configurations = configurations[:len(configurations)-RetainLaunchConfigurationCount()]
prefix := fmt.Sprintf("%s-", fi.StringValue(t.Name))
for _, configuration := range configurations {
removals = append(removals, &deleteLaunchTemplate{lc: configuration})
if strings.HasPrefix(aws.StringValue(configuration.LaunchTemplateName), prefix) {
removals = append(removals, &deleteLaunchTemplate{lc: configuration})
}
}
klog.V(2).Infof("will delete launch template: %v", removals)
return removals, nil
}

View File

@ -20,44 +20,36 @@ import (
"encoding/base64"
"fmt"
"sort"
"strings"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"k8s.io/klog/v2"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
)
// RenderAWS is responsible for performing creating / updating the launch template
func (t *LaunchTemplate) RenderAWS(c *awsup.AWSAPITarget, a, ep, changes *LaunchTemplate) error {
name := t.LaunchTemplateName()
func (t *LaunchTemplate) RenderAWS(c *awsup.AWSAPITarget, a, e, changes *LaunchTemplate) error {
// @step: resolve the image id to an AMI for us
image, err := c.Cloud.ResolveImage(fi.StringValue(t.ImageID))
if err != nil {
return err
}
// @step: lets build the launch template input
input := &ec2.CreateLaunchTemplateInput{
LaunchTemplateData: &ec2.RequestLaunchTemplateData{
DisableApiTermination: fi.Bool(false),
EbsOptimized: t.RootVolumeOptimization,
ImageId: image.ImageId,
InstanceType: t.InstanceType,
NetworkInterfaces: []*ec2.LaunchTemplateInstanceNetworkInterfaceSpecificationRequest{
{
AssociatePublicIpAddress: t.AssociatePublicIP,
DeleteOnTermination: aws.Bool(true),
DeviceIndex: fi.Int64(0),
},
// @step: lets build the launch template data
data := &ec2.RequestLaunchTemplateData{
DisableApiTermination: fi.Bool(false),
EbsOptimized: t.RootVolumeOptimization,
ImageId: image.ImageId,
InstanceType: t.InstanceType,
NetworkInterfaces: []*ec2.LaunchTemplateInstanceNetworkInterfaceSpecificationRequest{
{
AssociatePublicIpAddress: t.AssociatePublicIP,
DeleteOnTermination: aws.Bool(true),
DeviceIndex: fi.Int64(0),
},
},
LaunchTemplateName: aws.String(name),
}
lc := input.LaunchTemplateData
// @step: add the actual block device mappings
rootDevices, err := t.buildRootDevice(c.Cloud)
@ -74,54 +66,50 @@ func (t *LaunchTemplate) RenderAWS(c *awsup.AWSAPITarget, a, ep, changes *Launch
}
for _, x := range []map[string]*BlockDeviceMapping{rootDevices, ephemeralDevices, additionalDevices} {
for name, device := range x {
input.LaunchTemplateData.BlockDeviceMappings = append(input.LaunchTemplateData.BlockDeviceMappings, device.ToLaunchTemplateBootDeviceRequest(name))
data.BlockDeviceMappings = append(data.BlockDeviceMappings, device.ToLaunchTemplateBootDeviceRequest(name))
}
}
// @step: add the ssh key
if t.SSHKey != nil {
lc.KeyName = t.SSHKey.Name
data.KeyName = t.SSHKey.Name
}
// @step: add the security groups
for _, sg := range t.SecurityGroups {
lc.NetworkInterfaces[0].Groups = append(lc.NetworkInterfaces[0].Groups, sg.ID)
data.NetworkInterfaces[0].Groups = append(data.NetworkInterfaces[0].Groups, sg.ID)
}
// @step: add any tenancy details
if t.Tenancy != nil {
lc.Placement = &ec2.LaunchTemplatePlacementRequest{Tenancy: t.Tenancy}
data.Placement = &ec2.LaunchTemplatePlacementRequest{Tenancy: t.Tenancy}
}
// @step: set the instance monitoring
lc.Monitoring = &ec2.LaunchTemplatesMonitoringRequest{Enabled: fi.Bool(false)}
data.Monitoring = &ec2.LaunchTemplatesMonitoringRequest{Enabled: fi.Bool(false)}
if t.InstanceMonitoring != nil {
lc.Monitoring = &ec2.LaunchTemplatesMonitoringRequest{Enabled: t.InstanceMonitoring}
data.Monitoring = &ec2.LaunchTemplatesMonitoringRequest{Enabled: t.InstanceMonitoring}
}
// @step: add the iam instance profile
if t.IAMInstanceProfile != nil {
lc.IamInstanceProfile = &ec2.LaunchTemplateIamInstanceProfileSpecificationRequest{
data.IamInstanceProfile = &ec2.LaunchTemplateIamInstanceProfileSpecificationRequest{
Name: t.IAMInstanceProfile.Name,
}
}
// @step: add the tags
var tags []*ec2.Tag
if len(t.Tags) > 0 {
var tags []*ec2.Tag
for k, v := range t.Tags {
tags = append(tags, &ec2.Tag{
Key: aws.String(k),
Value: aws.String(v),
})
}
lc.TagSpecifications = append(lc.TagSpecifications, &ec2.LaunchTemplateTagSpecificationRequest{
data.TagSpecifications = append(data.TagSpecifications, &ec2.LaunchTemplateTagSpecificationRequest{
ResourceType: aws.String(ec2.ResourceTypeInstance),
Tags: tags,
})
lc.TagSpecifications = append(lc.TagSpecifications, &ec2.LaunchTemplateTagSpecificationRequest{
data.TagSpecifications = append(data.TagSpecifications, &ec2.LaunchTemplateTagSpecificationRequest{
ResourceType: aws.String(ec2.ResourceTypeVolume),
Tags: tags,
})
input.TagSpecifications = append(input.TagSpecifications, &ec2.TagSpecification{
ResourceType: aws.String(ec2.ResourceTypeLaunchTemplate),
Tags: tags,
})
}
// @step: add the userdata
if t.UserData != nil {
@ -129,7 +117,7 @@ func (t *LaunchTemplate) RenderAWS(c *awsup.AWSAPITarget, a, ep, changes *Launch
if err != nil {
return fmt.Errorf("error rendering LaunchTemplate UserData: %v", err)
}
lc.UserData = aws.String(base64.StdEncoding.EncodeToString(d))
data.UserData = aws.String(base64.StdEncoding.EncodeToString(d))
}
// @step: add market options
if fi.StringValue(t.SpotPrice) != "" {
@ -138,18 +126,40 @@ func (t *LaunchTemplate) RenderAWS(c *awsup.AWSAPITarget, a, ep, changes *Launch
InstanceInterruptionBehavior: t.InstanceInterruptionBehavior,
MaxPrice: t.SpotPrice,
}
lc.InstanceMarketOptions = &ec2.LaunchTemplateInstanceMarketOptionsRequest{
data.InstanceMarketOptions = &ec2.LaunchTemplateInstanceMarketOptionsRequest{
MarketType: fi.String("spot"),
SpotOptions: s,
}
}
// @step: attempt to create the launch template
if _, err = c.Cloud.EC2().CreateLaunchTemplate(input); err != nil {
return fmt.Errorf("error creating LaunchTemplate: %v", err)
if a == nil {
input := &ec2.CreateLaunchTemplateInput{
LaunchTemplateName: t.Name,
LaunchTemplateData: data,
TagSpecifications: []*ec2.TagSpecification{
{
ResourceType: aws.String(ec2.ResourceTypeLaunchTemplate),
Tags: tags,
},
},
}
output, err := c.Cloud.EC2().CreateLaunchTemplate(input)
if err != nil || output.LaunchTemplate == nil {
return fmt.Errorf("error creating LaunchTemplate %q: %v", fi.StringValue(t.Name), err)
}
e.ID = output.LaunchTemplate.LaunchTemplateId
} else {
// TODO: Update the LaunchTemplate tags
input := &ec2.CreateLaunchTemplateVersionInput{
LaunchTemplateName: t.Name,
LaunchTemplateData: data,
}
if _, err = c.Cloud.EC2().CreateLaunchTemplateVersion(input); err != nil {
return fmt.Errorf("error creating LaunchTemplateVersion: %v", err)
}
e.ID = a.ID
}
ep.ID = fi.String(name)
return nil
}
@ -161,7 +171,7 @@ func (t *LaunchTemplate) Find(c *fi.Context) (*LaunchTemplate, error) {
}
// @step: get the latest launch template version
lt, err := t.findLatestLaunchTemplate(c)
lt, err := t.findLatestLaunchTemplateVersion(c)
if err != nil {
return nil, err
}
@ -173,7 +183,7 @@ func (t *LaunchTemplate) Find(c *fi.Context) (*LaunchTemplate, error) {
actual := &LaunchTemplate{
AssociatePublicIP: fi.Bool(false),
ID: lt.LaunchTemplateName,
ID: lt.LaunchTemplateId,
ImageID: lt.LaunchTemplateData.ImageId,
InstanceMonitoring: fi.Bool(false),
InstanceType: lt.LaunchTemplateData.InstanceType,
@ -306,83 +316,39 @@ func (t *LaunchTemplate) findAllLaunchTemplates(c *fi.Context) ([]*ec2.LaunchTem
}
}
// findLaunchTemplates returns a list of launch templates
func (t *LaunchTemplate) findLaunchTemplates(c *fi.Context) ([]*ec2.LaunchTemplateVersion, error) {
// findLatestLaunchTemplateVersion returns the latest template
func (t *LaunchTemplate) findLatestLaunchTemplateVersion(c *fi.Context) (*ec2.LaunchTemplateVersion, error) {
cloud, ok := c.Cloud.(awsup.AWSCloud)
if !ok {
return []*ec2.LaunchTemplateVersion{}, fmt.Errorf("invalid cloud provider: %v, expected: awsup.AWSCloud", c.Cloud)
return nil, fmt.Errorf("invalid cloud provider: %v, expected: awsup.AWSCloud", c.Cloud)
}
// @step: get a list of the launch templates
templates, err := t.findAllLaunchTemplates(c)
input := &ec2.DescribeLaunchTemplateVersionsInput{
LaunchTemplateName: t.Name,
Versions: []*string{aws.String("$Latest")},
}
output, err := cloud.EC2().DescribeLaunchTemplateVersions(input)
if err != nil {
return nil, err
}
prefix := fmt.Sprintf("%s-", fi.StringValue(t.Name))
// @step: get the launch template versions for the templates we are interested in
var list []*ec2.LaunchTemplateVersion
var next *string
for _, x := range templates {
if strings.HasPrefix(aws.StringValue(x.LaunchTemplateName), prefix) {
err := func() error {
for {
resp, err := cloud.EC2().DescribeLaunchTemplateVersions(&ec2.DescribeLaunchTemplateVersionsInput{
LaunchTemplateName: x.LaunchTemplateName,
NextToken: next,
})
if err != nil {
return err
}
list = append(list, resp.LaunchTemplateVersions...)
if resp.NextToken == nil {
return nil
}
next = resp.NextToken
}
}()
if err != nil {
return nil, err
}
if awsup.AWSErrorCode(err) == "InvalidLaunchTemplateName.NotFoundException" {
klog.V(4).Infof("Got InvalidLaunchTemplateName.NotFoundException error describing latest launch template version: %q", aws.StringValue(t.Name))
return nil, nil
} else {
return nil, err
}
}
// @step: sort the configurations in chronological order
sort.Slice(list, func(i, j int) bool {
ti := list[i].CreateTime
tj := list[j].CreateTime
if tj == nil {
return true
}
if ti == nil {
return false
}
return ti.UnixNano() < tj.UnixNano()
})
return list, nil
}
// findLatestLaunchTemplate returns the latest template
func (t *LaunchTemplate) findLatestLaunchTemplate(c *fi.Context) (*ec2.LaunchTemplateVersion, error) {
// @step: get a list of configuration
configurations, err := t.findLaunchTemplates(c)
if err != nil {
return nil, err
}
if len(configurations) == 0 {
if len(output.LaunchTemplateVersions) == 0 {
return nil, nil
}
return configurations[len(configurations)-1], nil
return output.LaunchTemplateVersions[0], nil
}
// deleteLaunchTemplate tracks a LaunchConfiguration that we're going to delete
// It implements fi.Deletion
type deleteLaunchTemplate struct {
lc *ec2.LaunchTemplateVersion
lc *ec2.LaunchTemplate
}
var _ fi.Deletion = &deleteLaunchTemplate{}

View File

@ -694,52 +694,44 @@ func findAutoscalingGroupLaunchConfiguration(c AWSCloud, g *autoscaling.Group) (
}
// @check the launch template then
var launchTemplate *autoscaling.LaunchTemplateSpecification
if g.LaunchTemplate != nil {
name = aws.StringValue(g.LaunchTemplate.LaunchTemplateName)
version := aws.StringValue(g.LaunchTemplate.Version)
if name != "" {
launchTemplate := name + ":" + version
return launchTemplate, nil
}
launchTemplate = g.LaunchTemplate
} else if g.MixedInstancesPolicy != nil && g.MixedInstancesPolicy.LaunchTemplate != nil && g.MixedInstancesPolicy.LaunchTemplate.LaunchTemplateSpecification != nil {
launchTemplate = g.MixedInstancesPolicy.LaunchTemplate.LaunchTemplateSpecification
} else {
return "", fmt.Errorf("error finding launch template or configuration for autoscaling group: %s", aws.StringValue(g.AutoScalingGroupName))
}
// @check: ok, lets check the mixed instance policy
if g.MixedInstancesPolicy != nil {
if g.MixedInstancesPolicy.LaunchTemplate != nil {
if g.MixedInstancesPolicy.LaunchTemplate.LaunchTemplateSpecification != nil {
var version string
name = aws.StringValue(g.MixedInstancesPolicy.LaunchTemplate.LaunchTemplateSpecification.LaunchTemplateName)
//See what version the ASG is set to use
mixedVersion := aws.StringValue(g.MixedInstancesPolicy.LaunchTemplate.LaunchTemplateSpecification.Version)
//Correctly Handle Default and Latest Versions
if mixedVersion == "" || mixedVersion == "$Default" || mixedVersion == "$Latest" {
request := &ec2.DescribeLaunchTemplatesInput{
LaunchTemplateNames: []*string{&name},
}
dltResponse, err := c.EC2().DescribeLaunchTemplates(request)
if err != nil {
return "", fmt.Errorf("error describing launch templates: %v", err)
}
launchTemplate := dltResponse.LaunchTemplates[0]
if mixedVersion == "" || mixedVersion == "$Default" {
version = strconv.FormatInt(*launchTemplate.DefaultVersionNumber, 10)
} else {
version = strconv.FormatInt(*launchTemplate.LatestVersionNumber, 10)
}
} else {
version = mixedVersion
}
klog.V(4).Infof("Launch Template Version Specified By ASG: %v", mixedVersion)
klog.V(4).Infof("Launch Template Version we are using for compare: %v", version)
if name != "" {
launchTemplate := name + ":" + version
return launchTemplate, nil
}
}
}
name = aws.StringValue(launchTemplate.LaunchTemplateName)
if name == "" {
return "", fmt.Errorf("error finding launch template for autoscaling group: %s", aws.StringValue(g.AutoScalingGroupName))
}
return "", fmt.Errorf("error finding launch template or configuration for autoscaling group: %s", aws.StringValue(g.AutoScalingGroupName))
version := aws.StringValue(launchTemplate.Version)
//Correctly Handle Default and Latest Versions
klog.V(4).Infof("Launch Template Version Specified By ASG: %v", version)
if version == "" || version == "$Default" || version == "$Latest" {
input := &ec2.DescribeLaunchTemplatesInput{
LaunchTemplateNames: []*string{&name},
}
output, err := c.EC2().DescribeLaunchTemplates(input)
if err != nil {
return "", fmt.Errorf("error describing launch templates: %q", err)
}
if len(output.LaunchTemplates) == 0 {
return "", fmt.Errorf("error finding launch template by name: %q", name)
}
launchTemplate := output.LaunchTemplates[0]
if version == "" || version == "$Default" {
version = strconv.FormatInt(*launchTemplate.DefaultVersionNumber, 10)
} else {
version = strconv.FormatInt(*launchTemplate.LatestVersionNumber, 10)
}
}
klog.V(4).Infof("Launch Template Version used for compare: %q", version)
return fmt.Sprintf("%s:%s", name, version), nil
}
// findInstanceLaunchConfiguration is responsible for discoverying the launch configuration for an instance