From 152ee769ee89e73499e917c78bc82f588759587d Mon Sep 17 00:00:00 2001 From: James Kwon Date: Fri, 25 Aug 2023 19:31:25 -0400 Subject: [PATCH 1/2] Implement functionality to include/exclude resources by tags --- aws/resources/s3.go | 56 +++++++++++---------------------------------- config/config.go | 44 +++++++++++++++++++++++++++++++++++ util/tag.go | 24 +++++++++++++++++++ 3 files changed, 81 insertions(+), 43 deletions(-) create mode 100644 util/tag.go diff --git a/aws/resources/s3.go b/aws/resources/s3.go index ba709db8..8a91e444 100644 --- a/aws/resources/s3.go +++ b/aws/resources/s3.go @@ -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" @@ -43,13 +43,11 @@ 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) @@ -57,39 +55,20 @@ func (bucket *S3Buckets) getS3BucketTags(bucketName string) ([]map[string]string 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 @@ -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 } diff --git a/config/config.go b/config/config.go index 404e9e23..d9f2a170 100644 --- a/config/config.go +++ b/config/config.go @@ -5,11 +5,15 @@ import ( "path/filepath" "reflect" "regexp" + "strings" "time" "gopkg.in/yaml.v2" ) +const DefaultAwsResourceExclusionTagKey = "cloud-nuke-excluded" +const DefaultAwsResourceInclusionTagKey = "cloud-nuke-included" + // Config - the config object we pass around type Config struct { ACM ResourceType `yaml:"ACM"` @@ -109,6 +113,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 { @@ -184,6 +189,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 { @@ -200,11 +206,49 @@ 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) getInclusionTag() string { + if r.IncludeRule.Tag != nil { + return *r.IncludeRule.Tag + } + + return DefaultAwsResourceInclusionTagKey +} + +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 + } + } + + // Handle include rule first + inclusionTag := r.getInclusionTag() + if value, ok := tags[inclusionTag]; ok { + if strings.ToLower(value) == "true" { + return true + } + } + + 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 diff --git a/util/tag.go b/util/tag.go new file mode 100644 index 00000000..379d6a4f --- /dev/null +++ b/util/tag.go @@ -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 +} From 692036022543408829e5fcaa1ee5b440efb36a13 Mon Sep 17 00:00:00 2001 From: James Kwon Date: Sun, 27 Aug 2023 12:24:12 -0400 Subject: [PATCH 2/2] Only support tag for exclusion list --- config/config.go | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/config/config.go b/config/config.go index d9f2a170..fcc5b6ff 100644 --- a/config/config.go +++ b/config/config.go @@ -12,7 +12,6 @@ import ( ) const DefaultAwsResourceExclusionTagKey = "cloud-nuke-excluded" -const DefaultAwsResourceInclusionTagKey = "cloud-nuke-included" // Config - the config object we pass around type Config struct { @@ -214,14 +213,6 @@ func (r ResourceType) getExclusionTag() string { return DefaultAwsResourceExclusionTagKey } -func (r ResourceType) getInclusionTag() string { - if r.IncludeRule.Tag != nil { - return *r.IncludeRule.Tag - } - - return DefaultAwsResourceInclusionTagKey -} - func (r ResourceType) ShouldIncludeBasedOnTag(tags map[string]string) bool { // Handle exclude rule first exclusionTag := r.getExclusionTag() @@ -231,14 +222,6 @@ func (r ResourceType) ShouldIncludeBasedOnTag(tags map[string]string) bool { } } - // Handle include rule first - inclusionTag := r.getInclusionTag() - if value, ok := tags[inclusionTag]; ok { - if strings.ToLower(value) == "true" { - return true - } - } - return true }