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

Implement functionality to include/exclude resources by tags #570

Merged
merged 2 commits into from
Aug 28, 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
56 changes: 13 additions & 43 deletions aws/resources/s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package resources
import (
"fmt"
"github.com/gruntwork-io/cloud-nuke/telemetry"
"github.com/gruntwork-io/cloud-nuke/util"
commonTelemetry "github.com/gruntwork-io/go-commons/telemetry"
"math"
"strings"
"sync"
"time"

Expand Down Expand Up @@ -43,53 +43,32 @@ func (sb S3Buckets) getS3BucketRegion(bucketName string) (string, error) {
}

// getS3BucketTags returns S3 Bucket tags.
func (bucket *S3Buckets) getS3BucketTags(bucketName string) ([]map[string]string, error) {
func (bucket *S3Buckets) getS3BucketTags(bucketName string) (map[string]string, error) {
input := &s3.GetBucketTaggingInput{
Bucket: aws.String(bucketName),
}

tags := []map[string]string{}

// Please note that svc argument should be created from a session object which is
// in the same region as the bucket or GetBucketTagging will fail.
result, err := bucket.Client.GetBucketTagging(input)
if err != nil {
if aerr, ok := err.(awserr.Error); ok {
switch aerr.Code() {
case "NoSuchTagSet":
return tags, nil
return nil, nil
}
}
return tags, err
}

for _, tagSet := range result.TagSet {
tags = append(tags, map[string]string{"Key": *tagSet.Key, "Value": *tagSet.Value})
return nil, err
}

return tags, nil
}

// hasValidTags checks if bucket tags permit it to be in the deletion list.
func hasValidTags(bucketTags []map[string]string) bool {
// Exclude deletion of any buckets with cloud-nuke-excluded tags
if len(bucketTags) > 0 {
for _, tagSet := range bucketTags {
key := strings.ToLower(tagSet["Key"])
value := strings.ToLower(tagSet["Value"])
if key == AwsResourceExclusionTagKey && value == "true" {
return false
}
}
}
return true
return util.ConvertS3TagsToMap(result.TagSet), nil
}

// S3Bucket - represents S3 bucket
type S3Bucket struct {
Name string
CreationDate time.Time
Tags []map[string]string
Tags map[string]string
Error error
IsValid bool
InvalidReason string
Expand Down Expand Up @@ -194,23 +173,14 @@ func (sb S3Buckets) getBucketInfo(bucket *s3.Bucket, bucketCh chan<- *S3Bucket,
bucketCh <- &bucketData
return
}
bucketData.Tags = bucketTags
if !hasValidTags(bucketData.Tags) {
bucketData.InvalidReason = "Matched tag filter"
bucketCh <- &bucketData
return
}

// Check if the bucket is older than the required time
if !configObj.S3.ShouldInclude(config.ResourceValue{Time: &bucketData.CreationDate}) {
bucketData.InvalidReason = "Matched CreationDate filter"
bucketCh <- &bucketData
return
}

// Check if the bucket matches config file rules
if !configObj.S3.ShouldInclude(config.ResourceValue{Name: &bucketData.Name}) {
bucketData.InvalidReason = "Filtered by config file rules"
bucketData.Tags = bucketTags
if !configObj.S3.ShouldInclude(config.ResourceValue{
Time: &bucketData.CreationDate,
Name: &bucketData.Name,
Tags: bucketTags,
}) {
bucketData.InvalidReason = "filtered"
bucketCh <- &bucketData
return
}
Expand Down
27 changes: 27 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ import (
"path/filepath"
"reflect"
"regexp"
"strings"
"time"

"gopkg.in/yaml.v2"
)

const DefaultAwsResourceExclusionTagKey = "cloud-nuke-excluded"

// Config - the config object we pass around
type Config struct {
ACM ResourceType `yaml:"ACM"`
Expand Down Expand Up @@ -109,6 +112,7 @@ type FilterRule struct {
NamesRegExp []Expression `yaml:"names_regex"`
TimeAfter *time.Time `yaml:"time_after"`
TimeBefore *time.Time `yaml:"time_before"`
Tag *string `yaml:"tag"` // A tag to filter resources by. (e.g., If set under ExcludedRule, resources with this tag will be excluded).
}

type Expression struct {
Expand Down Expand Up @@ -184,6 +188,7 @@ func ShouldInclude(name string, includeREs []Expression, excludeREs []Expression
type ResourceValue struct {
Name *string
Time *time.Time
Tags map[string]string
}

func (r ResourceType) ShouldIncludeBasedOnTime(time time.Time) bool {
Expand All @@ -200,11 +205,33 @@ func (r ResourceType) ShouldIncludeBasedOnTime(time time.Time) bool {
return true
}

func (r ResourceType) getExclusionTag() string {
if r.ExcludeRule.Tag != nil {
return *r.ExcludeRule.Tag
}

return DefaultAwsResourceExclusionTagKey
}

func (r ResourceType) ShouldIncludeBasedOnTag(tags map[string]string) bool {
// Handle exclude rule first
exclusionTag := r.getExclusionTag()
if value, ok := tags[exclusionTag]; ok {
if strings.ToLower(value) == "true" {
return false
}
}

return true
}

func (r ResourceType) ShouldInclude(value ResourceValue) bool {
if value.Name != nil && !ShouldInclude(*value.Name, r.IncludeRule.NamesRegExp, r.ExcludeRule.NamesRegExp) {
return false
} else if value.Time != nil && !r.ShouldIncludeBasedOnTime(*value.Time) {
return false
} else if value.Tags != nil && len(value.Tags) != 0 && !r.ShouldIncludeBasedOnTag(value.Tags) {
return false
}

return true
Expand Down
24 changes: 24 additions & 0 deletions util/tag.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package util

import (
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/s3"
)

func ConvertS3TagsToMap(tags []*s3.Tag) map[string]string {
tagMap := make(map[string]string)
for _, tag := range tags {
tagMap[*tag.Key] = *tag.Value
}

return tagMap
}

func ConvertEC2TagsToMap(tags []*ec2.Tag) map[string]string {
tagMap := make(map[string]string)
for _, tag := range tags {
tagMap[*tag.Key] = *tag.Value
}

return tagMap
}