Skip to content

Commit 991f956

Browse files
committed
make AWS resource tagging optional via a config flag
1 parent 153e22f commit 991f956

File tree

6 files changed

+149
-40
lines changed

6 files changed

+149
-40
lines changed

cmd/main.go

Lines changed: 1 addition & 0 deletions
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

Lines changed: 7 additions & 0 deletions
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).

pkg/cloudprovider/aws/aws.go

Lines changed: 51 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ 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 the ASG
29+
// tagKey is the key for the tag applied to ASGs and Fleet requests
3030
tagKey = "k8s.io/atlassian-escalator/enabled"
31-
// tagValue is the value for the tag applied to the ASG
31+
// tagValue is the value for the tag applied to ASGs and Fleet requests
3232
tagValue = "true"
3333
)
3434

@@ -96,33 +96,7 @@ func (c *CloudProvider) RegisterNodeGroups(groups ...cloudprovider.NodeGroupConf
9696
continue
9797
}
9898

99-
// Search the asg for tagKey, then add it if it's not present
100-
hasTag := false
101-
tags := group.Tags
102-
for _, tag := range tags {
103-
if *tag.Key == tagKey {
104-
hasTag = true
105-
break
106-
}
107-
}
108-
if !hasTag {
109-
tagInput := &autoscaling.CreateOrUpdateTagsInput{
110-
Tags: []*autoscaling.Tag{
111-
{
112-
Key: awsapi.String(tagKey),
113-
PropagateAtLaunch: awsapi.Bool(true),
114-
ResourceId: awsapi.String(id),
115-
ResourceType: awsapi.String("auto-scaling-group"),
116-
Value: awsapi.String(tagValue),
117-
},
118-
},
119-
}
120-
log.WithField("asg", id).Infof("creating auto scaling tag")
121-
_, err := c.service.CreateOrUpdateTags(tagInput)
122-
if err != nil {
123-
log.Errorf("failed to create auto scaling tag for ASG %v", id)
124-
}
125-
}
99+
addASGTags(configs[id], group, c)
126100

127101
c.nodeGroups[id] = NewNodeGroup(configs[id], group, c)
128102
}
@@ -517,17 +491,6 @@ func createFleetInput(n NodeGroup, addCount int64) (*ec2.CreateFleetInput, error
517491
Overrides: launchTemplateOverrides,
518492
},
519493
},
520-
TagSpecifications: []*ec2.TagSpecification{
521-
{
522-
ResourceType: awsapi.String(ec2.ResourceTypeFleet),
523-
Tags: []*ec2.Tag{
524-
{
525-
Key: awsapi.String(tagKey),
526-
Value: awsapi.String(tagValue),
527-
},
528-
},
529-
},
530-
},
531494
}
532495

533496
if lifecycle == LifecycleOnDemand {
@@ -542,6 +505,20 @@ func createFleetInput(n NodeGroup, addCount int64) (*ec2.CreateFleetInput, error
542505
}
543506
}
544507

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+
545522
return fleetInput, nil
546523
}
547524

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

591568
return launchTemplateOverrides, nil
592569
}
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

Lines changed: 88 additions & 0 deletions
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/interface.go

Lines changed: 1 addition & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

0 commit comments

Comments
 (0)