Skip to content

Commit

Permalink
consider tags limits, move logic to a more appropriated place, re-add…
Browse files Browse the repository at this point in the history
… test for managernodegroup
  • Loading branch information
SlevinWasAlreadyTaken committed Apr 5, 2022
1 parent 266886e commit 65303d8
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 21 deletions.
1 change: 1 addition & 0 deletions pkg/cfn/builder/nodegroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

// MaximumTagNumber for ASGs as described here https://docs.aws.amazon.com/autoscaling/ec2/userguide/autoscaling-tagging.html
const MaximumTagNumber = 50
const MaximumCreatedTagNumberPerCall = 25

// NodeGroupResourceSet stores the resource information of the nodegroup
type NodeGroupResourceSet struct {
Expand Down
87 changes: 87 additions & 0 deletions pkg/cfn/manager/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/aws/aws-sdk-go-v2/service/cloudtrail"
"github.com/aws/aws-sdk-go-v2/service/cloudtrail/types"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/autoscaling"
"github.com/aws/aws-sdk-go/service/cloudformation"
"github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
Expand Down Expand Up @@ -237,6 +238,92 @@ func (c *StackCollection) createStackRequest(stackName string, resourceSet build
return stack, nil
}

func (c *StackCollection) PropagateManagedNodeGroupTagsToASG(ngName string, ngTags map[string]string, asgNames []string, errCh chan error) error {
go func() {
defer close(errCh)
// build the input tags for all ASGs attached to the managed nodegroup
asgTags := []*autoscaling.Tag{}

for _, asgName := range asgNames {
// skip directly if not tags are required to be created
if len(ngTags) == 0 {
continue
}

// check if the number of tags on the ASG would go over the defined limit
if err := c.checkASGTagsNumber(ngName, asgName, ngTags); err != nil {
errCh <- err
return
}
// build the list of tags to attach to the ASG
for ngTagKey, ngTagValue := range ngTags {
asgTag := &autoscaling.Tag{
ResourceId: aws.String(asgName),
ResourceType: aws.String("auto-scaling-group"),
Key: aws.String(ngTagKey),
Value: aws.String(ngTagValue),
PropagateAtLaunch: aws.Bool(false),
}
asgTags = append(asgTags, asgTag)
}
}

// consider the maximum number of tags we can create at once...
var chunkedASGTags [][]*autoscaling.Tag
chunkSize := builder.MaximumCreatedTagNumberPerCall
for start := 0; start < len(asgTags); start += chunkSize {
end := start + chunkSize
if end > len(asgTags) {
end = len(asgTags)
}
chunkedASGTags = append(chunkedASGTags, asgTags[start:end])
}
// ...then create all of them in a loop
for _, asgTags := range chunkedASGTags {
input := &autoscaling.CreateOrUpdateTagsInput{Tags: asgTags}
if _, err := c.asgAPI.CreateOrUpdateTags(input); err != nil {
errCh <- errors.Wrapf(err, "creating or updating asg tags for managed nodegroup %q", ngName)
return
}
}
errCh <- nil
}()
return nil
}

// checkASGTagsNumber limit considering the new propagated tags
func (c *StackCollection) checkASGTagsNumber(ngName, asgName string, propagatedTags map[string]string) error {
tagsFilter := &autoscaling.DescribeTagsInput{
Filters: []*autoscaling.Filter{
{
Name: aws.String("auto-scaling-group"),
Values: []*string{aws.String(asgName)},
},
},
}
output, err := c.asgAPI.DescribeTags(tagsFilter)
if err != nil {
return errors.Wrapf(err, "describing asg %q tags for managed nodegroup %q", asgName, ngName)
}
asgTags := output.Tags
// intersection of key tags to consider the number of tags going
// to be attached to the ASG
uniqueTagKeyCount := len(asgTags) + len(propagatedTags)
for ngTagKey := range propagatedTags {
for _, asgTag := range asgTags {
// decrease the unique tag key count if there is a match
if asgTag.Key != nil && *asgTag.Key == ngTagKey {
uniqueTagKeyCount--
break
}
}
}
if uniqueTagKeyCount > builder.MaximumTagNumber {
return fmt.Errorf("number of tags is exceeding the maximum amount for asg %d, was: %d", builder.MaximumTagNumber, uniqueTagKeyCount)
}
return nil
}

// UpdateStack will update a CloudFormation stack by creating and executing a ChangeSet
func (c *StackCollection) UpdateStack(options UpdateStackOptions) error {
logger.Info(options.Description)
Expand Down
24 changes: 4 additions & 20 deletions pkg/cfn/manager/nodegroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,31 +87,15 @@ func (c *StackCollection) propagateManagedNodeGroupTagsToASGTask(errorCh chan er
return errors.Wrapf(err, "couldn't get managed nodegroup details for nodegroup %q", ng.Name)
}

// set the managed nodegroup tags to all the ASGs found
if res.Nodegroup.Resources != nil {
// build the input tags for all ASGs attached to the managed nodegroup
asgTags := []*autoscaling.Tag{}

asgNames := []string{}
for _, asg := range res.Nodegroup.Resources.AutoScalingGroups {
for ngTagKey, ngTagValue := range ng.Tags {
asgTag := &autoscaling.Tag{
ResourceId: aws.String(*asg.Name),
ResourceType: aws.String("auto-scaling-group"),
Key: aws.String(ngTagKey),
Value: aws.String(ngTagValue),
PropagateAtLaunch: aws.Bool(false),
}
asgTags = append(asgTags, asgTag)
if asg.Name != nil && *asg.Name != "" {
asgNames = append(asgNames, *asg.Name)
}
}

input := &autoscaling.CreateOrUpdateTagsInput{Tags: asgTags}
if _, err := c.asgAPI.CreateOrUpdateTags(input); err != nil {
return errors.Wrapf(err, "creating or updating asg tags for managed nodegroup %q", ng.Name)
}
return c.PropagateManagedNodeGroupTagsToASG(ng.Name, ng.Tags, asgNames, errorCh)
}

go func() { errorCh <- nil }()
return nil
}

Expand Down
13 changes: 13 additions & 0 deletions pkg/cfn/manager/tasks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,19 @@ var _ = Describe("StackCollection Tasks", func() {
tasks := stackManager.NewTasksToCreateClusterWithNodeGroups(nil, nil)
Expect(tasks.Describe()).To(Equal(`1 task: { create cluster control plane "test-cluster" }`))
}
{
tasks := stackManager.NewTasksToCreateClusterWithNodeGroups(makeNodeGroups("bar", "foo"), makeManagedNodeGroups("m1", "m2"))
Expect(tasks.Describe()).To(Equal(`
2 sequential tasks: { create cluster control plane "test-cluster",
4 parallel sub-tasks: {
create nodegroup "bar",
create nodegroup "foo",
create managed nodegroup "m1",
create managed nodegroup "m2",
}
}
`))
}
{
tasks := stackManager.NewTasksToCreateClusterWithNodeGroups(makeNodeGroups("bar", "foo"), makeManagedNodeGroupsWithPropagatedTags("m1", "m2"))
Expect(tasks.Describe()).To(Equal(`
Expand Down
2 changes: 1 addition & 1 deletion userdocs/src/usage/autoscaling.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ nodeGroups:
k8s.io/cluster-autoscaler/node-template/taint/feaster: "true:NoSchedule"
```
For unmanaged and managed noderoups, this is done by `eksctl` automatically if `propagateASGTags` is set to `true` like this:
For unmanaged and managed nodegroups, this is done by `eksctl` automatically if `propagateASGTags` is set to `true` like this:

```yaml
nodeGroups:
Expand Down

0 comments on commit 65303d8

Please sign in to comment.