Skip to content

Commit

Permalink
Add support for tagging AutoScaling Groups (#197)
Browse files Browse the repository at this point in the history
* Add support for tagging AutoScaling Groups

* Document the ASG commands

* Propagate ASG tags when the
  • Loading branch information
cristim committed Feb 16, 2023
1 parent ca2ba27 commit 52be4fa
Show file tree
Hide file tree
Showing 4 changed files with 277 additions and 0 deletions.
16 changes: 16 additions & 0 deletions README.MD
Expand Up @@ -306,6 +306,22 @@ Read csv and tag ecr - `awstaghelper ecr tag-repository`
Example:
`awstaghelper ecr tag-repository --filename ecrTag.csv --profile main`

### AutoScaling groups

#### Get ASG tags

Get list of ASGs with required tags - `awstaghelper asg get-asg-tags`

Example:
`awstaghelper asg get-asg-tags --filename asgTags.csv --tags Name,Owner --profile main`

#### Tag ASGs

Read csv and tag ASGs - `awstaghelper asg tag-asg`

Example:
`awstaghelper asg tag-asg --filename asgTags.csv --profile main`

## Global parameters

`filename` - path where to write or read data. Supported by every option. Default `awsTags.csv`
Expand Down
72 changes: 72 additions & 0 deletions cmd/asg.go
@@ -0,0 +1,72 @@
/*
Copyright © 2023 Cristian Magherusan-Stanciu cristi@leanercloud.com
Copyright © 2020 Maksym Postument 777rip777@gmail.com
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Package cmd is the package for the CLI of awstaghelper
package cmd

import (
"github.com/aws/aws-sdk-go/service/autoscaling"
"github.com/mpostument/awstaghelper/pkg"

"github.com/spf13/cobra"
)

// asgCmd represents the asg command
var asgCmd = &cobra.Command{
Use: "asg",
Short: "Root command for interaction with AWS autoscaling groups",
Long: `Root command for interaction with AWS autoscaling groups.`,
}

var getASGCmd = &cobra.Command{
Use: "get-asg-tags",
Short: "Write ASG names and required tags to CSV",
Long: `Write to csv data with ASG name and required tags to CSV.
This CSV can be used with tag-asg command to tag aws environment.
Specify list of tags which should be read using tags flag: --tags Name,Env,Project.
Csv filename can be specified with flag filename.`,
Run: func(cmd *cobra.Command, args []string) {
tags, _ := cmd.Flags().GetString("tags")
filename, _ := cmd.Flags().GetString("filename")
profile, _ := cmd.Flags().GetString("profile")
region, _ := cmd.Flags().GetString("region")
sess := pkg.GetSession(region, profile)
client := autoscaling.New(sess)
pkg.WriteCsv(pkg.ParseASGTags(tags, client), filename)
},
}

var tagASGCmd = &cobra.Command{
Use: "tag-asg",
Short: "Read CSV and tag ASGs with CSV data",
Long: `Read CSV generated with get-asg-tags command and tag ASGs with tags from CSV.`,
Run: func(cmd *cobra.Command, args []string) {
filename, _ := cmd.Flags().GetString("filename")
profile, _ := cmd.Flags().GetString("profile")
region, _ := cmd.Flags().GetString("region")
sess := pkg.GetSession(region, profile)
csvData := pkg.ReadCsv(filename)
client := autoscaling.New(sess)
pkg.TagASG(csvData, client)
},
}

func init() {
rootCmd.AddCommand(asgCmd)
asgCmd.AddCommand(getASGCmd)
asgCmd.AddCommand(tagASGCmd)
}
90 changes: 90 additions & 0 deletions pkg/asg.go
@@ -0,0 +1,90 @@
package pkg

import (
"fmt"
"log"
"strconv"
"strings"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/autoscaling"
"github.com/aws/aws-sdk-go/service/autoscaling/autoscalingiface"
)

// getASGs return all ASGs from specified region
func getASGs(client autoscalingiface.AutoScalingAPI) []*autoscaling.Group {
input := &autoscaling.DescribeAutoScalingGroupsInput{}

var result []*autoscaling.Group

err := client.DescribeAutoScalingGroupsPages(input,
func(page *autoscaling.DescribeAutoScalingGroupsOutput, lastPage bool) bool {
result = append(result, page.AutoScalingGroups...)
return !lastPage
})
if err != nil {
log.Fatal("Not able to get ASGs ", err)
}
return result
}

// ParseASGTags parse output from getASGs and return ASG name and specified tags.
func ParseASGTags(tagsToRead string, client autoscalingiface.AutoScalingAPI) [][]string {
asgsOutput := getASGs(client)
rows := addHeadersToCsv(tagsToRead, "AutoScalingGroupName")
for _, asg := range asgsOutput {
tags := map[string]string{}
for _, tag := range asg.Tags {
tags[*tag.Key] = *tag.Value + fmt.Sprintf("|Propagate=%t", *tag.PropagateAtLaunch)
}
rows = addTagsToCsv(tagsToRead, tags, rows, *asg.AutoScalingGroupName)
}
return rows
}

// TagASG tag ASGs. Take as input data from csv file.
func TagASG(csvData [][]string, client autoscalingiface.AutoScalingAPI) {
for r := 1; r < len(csvData); r++ {
var tags []*autoscaling.Tag
for c := 1; c < len(csvData[0]); c++ {
val := strings.Split(csvData[r][c], "|Propagate=")
var propagate bool
var err error

switch len(val) {
case 1:
{
propagate = true
}
case 2:
{
propagate, err = strconv.ParseBool(val[1])
if err != nil {
propagate = true
}
}
default:
{
log.Printf("Invalid CSV format: %v", csvData[r][c])
}
}

tags = append(tags, &autoscaling.Tag{
Key: &csvData[0][c],
Value: &val[0],
PropagateAtLaunch: &propagate,
ResourceId: &csvData[r][0],
ResourceType: aws.String("auto-scaling-group"),
})
}

input := &autoscaling.CreateOrUpdateTagsInput{
Tags: tags,
}

_, err := client.CreateOrUpdateTags(input)
if awsErrorHandle(err) {
return
}
}
}
99 changes: 99 additions & 0 deletions pkg/asg_test.go
@@ -0,0 +1,99 @@
package pkg

import (
"testing"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/autoscaling"
"github.com/aws/aws-sdk-go/service/autoscaling/autoscalingiface"
"github.com/stretchr/testify/assert"
)

type mockedASG struct {
autoscalingiface.AutoScalingAPI
respDescribeAutoScalingGroups autoscaling.DescribeAutoScalingGroupsOutput
}

// AWS Mocks
func (m *mockedASG) DescribeAutoScalingGroupsPages(input *autoscaling.DescribeAutoScalingGroupsInput,
pageFunc func(*autoscaling.DescribeAutoScalingGroupsOutput, bool) bool) error {
pageFunc(&m.respDescribeAutoScalingGroups, true)
return nil
}

var parseASGTagsResponse = autoscaling.DescribeAutoScalingGroupsOutput{
AutoScalingGroups: []*autoscaling.Group{
{
AutoScalingGroupName: aws.String("asg1"),
Tags: []*autoscaling.TagDescription{
{
Key: aws.String("Name"),
Value: aws.String("ASG1"),
PropagateAtLaunch: aws.Bool(true),
ResourceId: aws.String("asg1"),
},
{
Key: aws.String("Environment"),
Value: aws.String("Test"),
PropagateAtLaunch: aws.Bool(false),
ResourceId: aws.String("asg1"),
},
},
},
{
AutoScalingGroupName: aws.String("asg2"),
Tags: []*autoscaling.TagDescription{
{
Key: aws.String("Name"),
Value: aws.String("ASG2"),
PropagateAtLaunch: aws.Bool(true),
ResourceId: aws.String("asg2"),
},
{
Key: aws.String("Environment"),
Value: aws.String("Dev"),
PropagateAtLaunch: aws.Bool(false),
ResourceId: aws.String("asg2"),
},
},
},
},
}

func Test_getASGs(t *testing.T) {
cases := []*mockedASG{
{
respDescribeAutoScalingGroups: parseASGTagsResponse,
},
}

expectedResult := parseASGTagsResponse.AutoScalingGroups
for _, c := range cases {
t.Run("GetASGs", func(t *testing.T) {
result := getASGs(c)
assertions := assert.New(t)
assertions.EqualValues(expectedResult, result)
})

}
}

func TestParseASGTags(t *testing.T) {
cases := []*mockedASG{
{
respDescribeAutoScalingGroups: parseASGTagsResponse,
},
}
expectedResult := [][]string{
{"AutoScalingGroupName", "Name", "Environment"},
{"asg1", "ASG1|Propagate=true", "Test|Propagate=false"},
{"asg2", "ASG2|Propagate=true", "Dev|Propagate=false"},
}
for _, c := range cases {
t.Run("ParseASGTags", func(t *testing.T) {
result := ParseASGTags("Name,Environment", c)
assertions := assert.New(t)
assertions.EqualValues(expectedResult, result)
})
}
}

0 comments on commit 52be4fa

Please sign in to comment.