Skip to content

Commit ee864bf

Browse files
authoredJul 14, 2020
Merge pull request #193 from haugenj/master
Add tags to Auto Scaling Group and Fleet request
2 parents bd128aa + 991f956 commit ee864bf

File tree

9 files changed

+193
-4
lines changed

9 files changed

+193
-4
lines changed
 

‎cmd/main.go

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ func setupCloudProvider(nodegroups []controller.NodeGroupOptions) cloudprovider.
7777
FleetInstanceReadyTimeout: n.AWS.FleetInstanceReadyTimeoutDuration(),
7878
Lifecycle: n.AWS.Lifecycle,
7979
InstanceTypeOverrides: n.AWS.InstanceTypeOverrides,
80+
ResourceTagging: n.AWS.ResourceTagging,
8081
},
8182
})
8283
}

‎docs/configuration/nodegroup.md

+7
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ node_groups:
3232
launch_template_id: "1"
3333
lifecycle: on-demand
3434
instance_type_overrides: ["t2.large", "t3.large"]
35+
resource_tagging: false
3536
```
3637
3738
## Options
@@ -242,3 +243,9 @@ Dependent on Launch Template ID being specified.
242243
An optional list of instance types to override the instance type within the launch template. Providing multiple instance
243244
types here increases the likelihood of a Spot request being successful. If omitted the instance type to request will
244245
be taken from the launch template.
246+
247+
### `aws.resource_tagging`
248+
249+
Tag ASG and Fleet Request resources used by Escalator with the metatdata key-value pair
250+
`k8s.io/atlassian-escalator/enabled`:`true`. Tagging doesn't alter the functionality of Escalator. Read more about
251+
tagging your AWS resources [here](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Using_Tags.html).

‎docs/deployment/aws/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Escalator requires the following IAM policy to be able to properly integrate wit
1919
"Effect": "Allow",
2020
"Action": [
2121
"autoscaling:AttachInstances",
22+
"autoscaling:CreateOrUpdateTags",
2223
"autoscaling:DescribeAutoScalingGroups",
2324
"autoscaling:SetDesiredCapacity",
2425
"autoscaling:TerminateInstanceInAutoScalingGroup",

‎pkg/cloudprovider/aws/aws.go

+54
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ const (
2626
LifecycleSpot = "spot"
2727
// The AttachInstances API only supports adding 20 instances at a time
2828
batchSize = 20
29+
// tagKey is the key for the tag applied to ASGs and Fleet requests
30+
tagKey = "k8s.io/atlassian-escalator/enabled"
31+
// tagValue is the value for the tag applied to ASGs and Fleet requests
32+
tagValue = "true"
2933
)
3034

3135
func instanceToProviderID(instance *autoscaling.Instance) string {
@@ -92,6 +96,8 @@ func (c *CloudProvider) RegisterNodeGroups(groups ...cloudprovider.NodeGroupConf
9296
continue
9397
}
9498

99+
addASGTags(configs[id], group, c)
100+
95101
c.nodeGroups[id] = NewNodeGroup(configs[id], group, c)
96102
}
97103

@@ -499,6 +505,20 @@ func createFleetInput(n NodeGroup, addCount int64) (*ec2.CreateFleetInput, error
499505
}
500506
}
501507

508+
if n.config.AWSConfig.ResourceTagging {
509+
fleetInput.TagSpecifications = []*ec2.TagSpecification{
510+
{
511+
ResourceType: awsapi.String(ec2.ResourceTypeFleet),
512+
Tags: []*ec2.Tag{
513+
{
514+
Key: awsapi.String(tagKey),
515+
Value: awsapi.String(tagValue),
516+
},
517+
},
518+
},
519+
}
520+
}
521+
502522
return fleetInput, nil
503523
}
504524

@@ -547,3 +567,37 @@ func createTemplateOverrides(n NodeGroup) ([]*ec2.FleetLaunchTemplateOverridesRe
547567

548568
return launchTemplateOverrides, nil
549569
}
570+
571+
// addASGTags will search an ASG for the tagKey and add the tag if it's not found
572+
func addASGTags(config *cloudprovider.NodeGroupConfig, asg *autoscaling.Group, provider *CloudProvider) {
573+
if !config.AWSConfig.ResourceTagging {
574+
return
575+
}
576+
577+
tags := asg.Tags
578+
for _, tag := range tags {
579+
if *tag.Key == tagKey {
580+
return
581+
}
582+
}
583+
584+
id := awsapi.StringValue(asg.AutoScalingGroupName)
585+
586+
tagInput := &autoscaling.CreateOrUpdateTagsInput{
587+
Tags: []*autoscaling.Tag{
588+
{
589+
Key: awsapi.String(tagKey),
590+
PropagateAtLaunch: awsapi.Bool(true),
591+
ResourceId: awsapi.String(id),
592+
ResourceType: awsapi.String("auto-scaling-group"),
593+
Value: awsapi.String(tagValue),
594+
},
595+
},
596+
}
597+
598+
log.WithField("asg", id).Infof("creating auto scaling tag")
599+
_, err := provider.service.CreateOrUpdateTags(tagInput)
600+
if err != nil {
601+
log.Errorf("failed to create auto scaling tag for ASG %v", id)
602+
}
603+
}

‎pkg/cloudprovider/aws/aws_test.go

+88
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ func setupAWSMocks() {
2828
MaxSize: aws.Int64(int64(25)),
2929
DesiredCapacity: aws.Int64(int64(1)),
3030
VPCZoneIdentifier: aws.String("subnetID-1,subnetID-2"),
31+
Tags: []*autoscaling.TagDescription{},
3132
}
3233

3334
mockAWSConfig = cloudprovider.AWSNodeGroupConfig{
@@ -36,6 +37,7 @@ func setupAWSMocks() {
3637
FleetInstanceReadyTimeout: tickerTimeout,
3738
Lifecycle: LifecycleOnDemand,
3839
InstanceTypeOverrides: []string{"instance-1", "instance-2"},
40+
ResourceTagging: false,
3941
}
4042

4143
mockNodeGroup = NodeGroup{
@@ -128,6 +130,29 @@ func TestCreateFleetInput(t *testing.T) {
128130
}
129131
}
130132

133+
func TestCreateFleetInput_WithResourceTagging(t *testing.T) {
134+
setupAWSMocks()
135+
autoScalingGroups := []*autoscaling.Group{&mockASG}
136+
nodeGroups := map[string]*NodeGroup{mockNodeGroup.id: &mockNodeGroup}
137+
addCount := int64(2)
138+
139+
awsCloudProvider, _ := newMockCloudProviderUsingInjection(
140+
nodeGroups,
141+
&test.MockAutoscalingService{
142+
DescribeAutoScalingGroupsOutput: &autoscaling.DescribeAutoScalingGroupsOutput{
143+
AutoScalingGroups: autoScalingGroups,
144+
},
145+
},
146+
&test.MockEc2Service{},
147+
)
148+
mockNodeGroup.provider = awsCloudProvider
149+
mockAWSConfig.ResourceTagging = true
150+
mockNodeGroupConfig.AWSConfig = mockAWSConfig
151+
152+
_, err := createFleetInput(mockNodeGroup, addCount)
153+
assert.Nil(t, err, "Expected no error from createFleetInput")
154+
}
155+
131156
func TestCreateTemplateOverrides_FailedCall(t *testing.T) {
132157
setupAWSMocks()
133158
expectedError := errors.New("call failed")
@@ -234,3 +259,66 @@ func TestCreateTemplateOverrides_NoInstanceTypeOverrides_Success(t *testing.T) {
234259
_, err := createTemplateOverrides(mockNodeGroup)
235260
assert.Nil(t, err, "Expected no error from createTemplateOverrides")
236261
}
262+
func TestAddASGTags_ResourceTaggingFalse(t *testing.T) {
263+
setupAWSMocks()
264+
mockNodeGroupConfig.AWSConfig.ResourceTagging = false
265+
awsCloudProvider, _ := newMockCloudProviderUsingInjection(
266+
nil,
267+
&test.MockAutoscalingService{},
268+
&test.MockEc2Service{},
269+
)
270+
addASGTags(&mockNodeGroupConfig, &mockASG, awsCloudProvider)
271+
}
272+
273+
func TestAddASGTags_ResourceTaggingTrue(t *testing.T) {
274+
setupAWSMocks()
275+
mockNodeGroupConfig.AWSConfig.ResourceTagging = true
276+
277+
// Mock service call
278+
awsCloudProvider, _ := newMockCloudProviderUsingInjection(
279+
nil,
280+
&test.MockAutoscalingService{
281+
CreateOrUpdateTagsOutput: &autoscaling.CreateOrUpdateTagsOutput{},
282+
},
283+
&test.MockEc2Service{},
284+
)
285+
addASGTags(&mockNodeGroupConfig, &mockASG, awsCloudProvider)
286+
}
287+
288+
func TestAddASGTags_ASGAlreadyTagged(t *testing.T) {
289+
setupAWSMocks()
290+
mockNodeGroupConfig.AWSConfig.ResourceTagging = true
291+
292+
// Mock existing tags
293+
key := tagKey
294+
asgTag := autoscaling.TagDescription{
295+
Key: &key,
296+
}
297+
mockASG.Tags = append(mockASG.Tags, &asgTag)
298+
299+
// Mock service call
300+
awsCloudProvider, _ := newMockCloudProviderUsingInjection(
301+
nil,
302+
&test.MockAutoscalingService{
303+
CreateOrUpdateTagsOutput: &autoscaling.CreateOrUpdateTagsOutput{},
304+
},
305+
&test.MockEc2Service{},
306+
)
307+
addASGTags(&mockNodeGroupConfig, &mockASG, awsCloudProvider)
308+
}
309+
310+
func TestAddASGTags_WithErrorResponse(t *testing.T) {
311+
setupAWSMocks()
312+
mockNodeGroupConfig.AWSConfig.ResourceTagging = true
313+
314+
// Mock service call and error
315+
awsCloudProvider, _ := newMockCloudProviderUsingInjection(
316+
nil,
317+
&test.MockAutoscalingService{
318+
CreateOrUpdateTagsOutput: &autoscaling.CreateOrUpdateTagsOutput{},
319+
CreateOrUpdateTagsErr: errors.New("unauthorized"),
320+
},
321+
&test.MockEc2Service{},
322+
)
323+
addASGTags(&mockNodeGroupConfig, &mockASG, awsCloudProvider)
324+
}

‎pkg/cloudprovider/aws/cloud_provider_test.go

+32-4
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,12 @@ func TestCloudProvider_GetNodeGroup(t *testing.T) {
9191

9292
func TestCloudProvider_RegisterNodeGroups(t *testing.T) {
9393
tests := []struct {
94-
name string
95-
nodeGroups map[string]bool
96-
response *autoscaling.DescribeAutoScalingGroupsOutput
97-
err error
94+
name string
95+
nodeGroups map[string]bool
96+
response *autoscaling.DescribeAutoScalingGroupsOutput
97+
err error
98+
tagResponse *autoscaling.CreateOrUpdateTagsOutput
99+
tagErr error
98100
}{
99101
{
100102
"register node group that does not exist",
@@ -103,6 +105,8 @@ func TestCloudProvider_RegisterNodeGroups(t *testing.T) {
103105
},
104106
&autoscaling.DescribeAutoScalingGroupsOutput{},
105107
nil,
108+
&autoscaling.CreateOrUpdateTagsOutput{},
109+
nil,
106110
},
107111
{
108112
"register node groups that exist",
@@ -121,6 +125,8 @@ func TestCloudProvider_RegisterNodeGroups(t *testing.T) {
121125
},
122126
},
123127
nil,
128+
&autoscaling.CreateOrUpdateTagsOutput{},
129+
nil,
124130
},
125131
{
126132
"register node groups, some don't exist",
@@ -136,12 +142,32 @@ func TestCloudProvider_RegisterNodeGroups(t *testing.T) {
136142
},
137143
},
138144
nil,
145+
&autoscaling.CreateOrUpdateTagsOutput{},
146+
nil,
139147
},
140148
{
141149
"register no node groups",
142150
map[string]bool{},
143151
&autoscaling.DescribeAutoScalingGroupsOutput{},
144152
fmt.Errorf("no groups"),
153+
&autoscaling.CreateOrUpdateTagsOutput{},
154+
nil,
155+
},
156+
{
157+
"register existing node group with error from CreateOrUpdateTags",
158+
map[string]bool{
159+
"1": true,
160+
},
161+
&autoscaling.DescribeAutoScalingGroupsOutput{
162+
AutoScalingGroups: []*autoscaling.Group{
163+
{
164+
AutoScalingGroupName: aws.String("1"),
165+
},
166+
},
167+
},
168+
nil,
169+
&autoscaling.CreateOrUpdateTagsOutput{},
170+
fmt.Errorf("unauthorized operation"),
145171
},
146172
}
147173

@@ -151,6 +177,8 @@ func TestCloudProvider_RegisterNodeGroups(t *testing.T) {
151177
service := test.MockAutoscalingService{
152178
DescribeAutoScalingGroupsOutput: tt.response,
153179
DescribeAutoScalingGroupsErr: tt.err,
180+
CreateOrUpdateTagsOutput: tt.tagResponse,
181+
CreateOrUpdateTagsErr: tt.tagErr,
154182
}
155183

156184
var ids []string

‎pkg/cloudprovider/interface.go

+1
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,5 @@ type AWSNodeGroupConfig struct {
117117
FleetInstanceReadyTimeout time.Duration
118118
Lifecycle string
119119
InstanceTypeOverrides []string
120+
ResourceTagging bool
120121
}

‎pkg/controller/node_group.go

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ type AWSNodeGroupOptions struct {
5959
FleetInstanceReadyTimeout string `json:"fleet_instance_ready_timeout,omitempty" yaml:"fleet_instance_ready_timeout,omitempty"`
6060
Lifecycle string `json:"lifecycle,omitempty" yaml:"lifecycle,omitempty"`
6161
InstanceTypeOverrides []string `json:"instance_type_overrides,omitempty" yaml:"instance_type_overrides,omitempty"`
62+
ResourceTagging bool `json:"resource_tagging,omitempty" yaml:"resource_tagging,omitempty"`
6263

6364
// Private variables for storing the parsed duration from the string
6465
fleetInstanceReadyTimeout time.Duration

‎pkg/test/aws.go

+8
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ type MockAutoscalingService struct {
1616
AttachInstanceOutput *autoscaling.AttachInstancesOutput
1717
AttachInstanceErr error
1818

19+
CreateOrUpdateTagsOutput *autoscaling.CreateOrUpdateTagsOutput
20+
CreateOrUpdateTagsErr error
21+
1922
DescribeAutoScalingGroupsOutput *autoscaling.DescribeAutoScalingGroupsOutput
2023
DescribeAutoScalingGroupsErr error
2124

@@ -31,6 +34,11 @@ func (m MockAutoscalingService) AttachInstances(*autoscaling.AttachInstancesInpu
3134
return m.AttachInstanceOutput, m.AttachInstanceErr
3235
}
3336

37+
// CreateOrUpdateTags mock implementation for MockAutoscalingService
38+
func (m MockAutoscalingService) CreateOrUpdateTags(*autoscaling.CreateOrUpdateTagsInput) (*autoscaling.CreateOrUpdateTagsOutput, error) {
39+
return m.CreateOrUpdateTagsOutput, m.CreateOrUpdateTagsErr
40+
}
41+
3442
// DescribeAutoScalingGroups mock implementation for MockAutoscalingService
3543
func (m MockAutoscalingService) DescribeAutoScalingGroups(*autoscaling.DescribeAutoScalingGroupsInput) (*autoscaling.DescribeAutoScalingGroupsOutput, error) {
3644
return m.DescribeAutoScalingGroupsOutput, m.DescribeAutoScalingGroupsErr

0 commit comments

Comments
 (0)
Failed to load comments.