Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for tagging EBS volumes #199

Merged
merged 1 commit into from
Feb 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 [email protected]
Copyright © 2020 Maksym Postument [email protected]

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 [email protected]
Copyright © 2020 Maksym Postument [email protected]

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 [email protected]
Copyright © 2020 Maksym Postument [email protected]

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)
})
}
}