Skip to content

Commit

Permalink
Add support for tagging EBS volumes (#199)
Browse files Browse the repository at this point in the history
  • Loading branch information
cristim committed Feb 19, 2023
1 parent 20556f9 commit 43ad4ae
Show file tree
Hide file tree
Showing 5 changed files with 278 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*.dll
*.so
*.dylib
awstaghelper

# Test binary, built with `go test -c`
*.test
Expand Down
18 changes: 18 additions & 0 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ Tags are critical to managing AWS resources at scale. Awstaghelper provides a co
* [Iam Role](#iam-role)
* [Elastic Beanstalk](#elastic-beanstalk)
* [ECR](#ecr)
* [AutoScaling groups](#autoscaling-groups)
* [EBS Volumes](#ebs-volumes)
* [Global parameters](#global-parameters)
* [Contributing](#contributing)
* [License](#license)
Expand Down Expand Up @@ -322,6 +324,22 @@ Read csv and tag ASGs - `awstaghelper asg tag-asg`
Example:
`awstaghelper asg tag-asg --filename asgTags.csv --profile main`

### EBS Volumes

#### Get EBS volume tags

Get list of EBS volumes with required tags - `awstaghelper ebs get-ebs-tags`

Example:
`awstaghelper ebs get-ebs-tags --filename ebsTags.csv --tags Name,Owner --profile main`

#### Tag EBS volumes

Read csv and tag EBS volumes - `awstaghelper ebs tag-ebs`

Example:
`awstaghelper ebs tag-ebs --filename ebsTags.csv --profile main`

## Global parameters

`filename` - path where to write or read data. Supported by every option. Default `awsTags.csv`
Expand Down
69 changes: 69 additions & 0 deletions cmd/ebs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
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/ec2"
"github.com/mpostument/awstaghelper/pkg"

"github.com/spf13/cobra"
)

// ebsCmd represents the ebs command
var ebsCmd = &cobra.Command{
Use: "ebs",
Short: "Root command for interaction with AWS EBS volumes",
Long: `Root command for interaction with AWS EBS volumes,`,
}

var getEBSVolumeCmd = &cobra.Command{
Use: "get-ebs-tags",
Short: "Write EBS volume IDs and required tags to CSV",
Long: `Write to csv data with EBS volume IDs and required tags to CSV. This CSV can be used with tag-ebs 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 := ec2.New(sess)
pkg.WriteCsv(pkg.ParseEBSVolumeTags(tags, client), filename)
},
}

var tagEBSVolumeCmd = &cobra.Command{
Use: "tag-ebs",
Short: "Read CSV and tag EBS volumes with CSV data",
Long: `Read CSV generated with get-ebs-tags command and tag EBS volumes 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 := ec2.New(sess)
pkg.TagEBSVolumes(csvData, client)
},
}

func init() {
rootCmd.AddCommand(ebsCmd)
ebsCmd.AddCommand(getEBSVolumeCmd)
ebsCmd.AddCommand(tagEBSVolumeCmd)
}
82 changes: 82 additions & 0 deletions pkg/ebs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
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 pkg

import (
"log"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
)

// getEBSVolumes returns all EBS volumes from specified region
func getEBSVolumes(client ec2iface.EC2API) []*ec2.Volume {
input := &ec2.DescribeVolumesInput{}

var result []*ec2.Volume

err := client.DescribeVolumesPages(input,
func(page *ec2.DescribeVolumesOutput, lastPage bool) bool {
result = append(result, page.Volumes...)
return !lastPage
})
if err != nil {
log.Fatal("Not able to get EBS volumes ", err)
}
return result
}

// ParseEBSVolumeTags parse output from getEBSVolumes and return volume ID and specified tags.
func ParseEBSVolumeTags(tagsToRead string, client ec2iface.EC2API) [][]string {
volumesOutput := getEBSVolumes(client)
rows := addHeadersToCsv(tagsToRead, "VolumeId")
for _, volume := range volumesOutput {
tags := map[string]string{}
for _, tag := range volume.Tags {
tags[*tag.Key] = *tag.Value
}
rows = addTagsToCsv(tagsToRead, tags, rows, *volume.VolumeId)
}
return rows
}

// TagEBSVolumes tag EBS volumes. Take as input data from csv file. Where first column is volume ID.
func TagEBSVolumes(csvData [][]string, client ec2iface.EC2API) {
for r := 1; r < len(csvData); r++ {
var tags []*ec2.Tag
for c := 1; c < len(csvData[0]); c++ {
tags = append(tags, &ec2.Tag{
Key: &csvData[0][c],
Value: &csvData[r][c],
})
}

input := &ec2.CreateTagsInput{
Resources: []*string{
aws.String(csvData[r][0]),
},
Tags: tags,
}

_, err := client.CreateTags(input)
if awsErrorHandle(err) {
return
}
}
}
108 changes: 108 additions & 0 deletions pkg/ebs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
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 pkg

import (
"testing"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
"github.com/stretchr/testify/assert"
)

type mockedEBS struct {
ec2iface.EC2API
respDescribeVolumes ec2.DescribeVolumesOutput
}

// AWS Mocks
func (m *mockedEBS) DescribeVolumesPages(input *ec2.DescribeVolumesInput,
pageFunc func(*ec2.DescribeVolumesOutput, bool) bool) error {
pageFunc(&m.respDescribeVolumes, true)
return nil
}

var ParseEBSVolumeTagsResponse = ec2.DescribeVolumesOutput{
Volumes: []*ec2.Volume{
{
VolumeId: aws.String("vol-1"),
Tags: []*ec2.Tag{
{
Key: aws.String("Name"),
Value: aws.String("Volume1"),
},
{
Key: aws.String("Environment"),
Value: aws.String("Test"),
},
},
},
{
VolumeId: aws.String("vol-2"),
Tags: []*ec2.Tag{
{
Key: aws.String("Name"),
Value: aws.String("Volume2"),
},
{
Key: aws.String("Environment"),
Value: aws.String("Dev"),
},
},
},
},
}

func Test_getEBSVolumes(t *testing.T) {
cases := []*mockedEBS{
{
respDescribeVolumes: ParseEBSVolumeTagsResponse,
},
}

expectedResult := ParseEBSVolumeTagsResponse.Volumes
for _, c := range cases {
t.Run("getEBSVolumes", func(t *testing.T) {
result := getEBSVolumes(c)
assertions := assert.New(t)
assertions.EqualValues(expectedResult, result)
})

}
}

func TestParseEBSVolumeTags(t *testing.T) {
cases := []*mockedEBS{
{
respDescribeVolumes: ParseEBSVolumeTagsResponse,
},
}
expectedResult := [][]string{
{"VolumeId", "Name", "Environment"},
{"vol-1", "Volume1", "Test"},
{"vol-2", "Volume2", "Dev"},
}
for _, c := range cases {
t.Run("ParseEBSVolumeTags", func(t *testing.T) {
result := ParseEBSVolumeTags("Name,Environment", c)
assertions := assert.New(t)
assertions.EqualValues(expectedResult, result)
})
}
}

0 comments on commit 43ad4ae

Please sign in to comment.