Skip to content

Commit

Permalink
Fix using matchTags and matchNoTags together
Browse files Browse the repository at this point in the history
  • Loading branch information
jckuester committed May 11, 2020
1 parent 24a0f0d commit be6ede7
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 63 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ test: ## Run unit tests
.PHONY: test-all
test-all: ## Run tests (including acceptance and integration tests)
go clean -testcache ${PKG_LIST}
./bin/go-acc ${PKG_LIST} -- -v -$(TESTARGS) -p 1 -race -timeout 30m
./bin/go-acc ${PKG_LIST} -- -v $(TESTARGS) -p 1 -race -timeout 30m

.PHONY: build
build: ## Build binary
Expand Down
48 changes: 23 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=for-the-badge)](/LICENSE.md)
[![Travis](https://img.shields.io/travis/cloudetc/awsweeper/master.svg?style=for-the-badge)](https://travis-ci.org/cloudetc/awsweeper)

AWSweeper cleans out all (or part) of the resources in your AWS account. Resources can be deleted by type, ID, tags, or
creation date using [regular expressions](https://golang.org/pkg/regexp/syntax/) declared via a filter in a YAML file
(see example snippets of a [filter.yml](#filter) below).
AWSweeper cleans out all (or parts) of the resources in your AWS account. Resources to be deleted can be filtered by
their ID, tags or creation date using [regular expressions](https://golang.org/pkg/regexp/syntax/) declared via a filter
in a YAML file (see [filter.yml](example/config.yml) as an example).

AWSweeper [can delete many](#supported-resources), but not all resources yet. Your help
supporting more resources is very much appreciated ([please read this issue](https://github.com/cloudetc/awsweeper/issues/21)
to support more resources is very much appreciated ([please read this issue](https://github.com/cloudetc/awsweeper/issues/21)
to see how easy it is).

Happy erasing!
Expand All @@ -41,33 +41,30 @@ To see options available run `awsweeper --help`.

## Filter

Delete resources via a filter declared in a YAML file.

The following filter deletes all EC2 instances that ID matches `^foo.*` and that have been created between
`2018-06-28 12:28:39` and `2018-10-14` UTC (instance filter part 1); additionally, EC2 instances having a tag
`foo: bar` *AND* not a tag key `owner` with arbitrary value are deleted (instance filter part 2); last but not least,
ALL security groups are deleted.
Resources are deleted via a filter declared in a YAML file.

aws_instance:

# instance filter part 1
- id: ^foo.*
created:
before: 2018-10-14
after: 2018-06-28 12:28:39
# instance filter part 2
- tags:
foo: bar
NOT(owner): .*
aws_security_groups:

The filter snippet above deletes all EC2 instances that ID matches `^foo.*` and that have been created between
`2018-06-28 12:28:39` and `2018-10-14` UTC (instance filter part 1); additionally, EC2 instances having a tag
`foo: bar` *AND* not a tag key `owner` with any value are deleted (instance filter part 2); last but not least,
ALL security groups are deleted by this filter.

The general filter syntax is as follows:

<resource type>:

# filter 1
- id: <regex to filter by id> | NOT(<regex to filter by id>)
tagged: bool (optional)
tags:
Expand All @@ -76,10 +73,8 @@ The general filter syntax is as follows:
created:
before: <timestamp> (optional)
after: <timestamp> (optional)

# filter 2
# OR
- ...

<resource type>:
...

Expand All @@ -88,34 +83,37 @@ Here is a more detailed description of the various ways to filter resources:
##### 1) Delete all resources of a particular type

[Terraform resource type indentifiers](https://www.terraform.io/docs/providers/aws/index.html) are used to delete
resources by type.

The following filter deletes *ALL* security groups, IAM roles, and EC2 instances:
resources by type. The following filter snippet deletes *ALL* security groups, IAM roles, and EC2 instances:

aws_security_group:
aws_iam_role:
aws_instance:

Don't forget the `:` at the end of each line.
Don't forget the `:` at the end of each line. Use the [all.yml](./all.yml), to delete all (currently supported)
resources.

##### 2) Delete by tags

If most of your resources have tags, this is probably the best way to filter them
for deletion. **Be aware**: Not all resources [support tags](#supported-resources) yet and can be filtered this way.

`tagged: false` deletes all resources that have no tags. Contrary, resources with any tags can be deleted with `tagged: true`.


The key and the value part of the tag filter can be negated by a surrounding `NOT(...)`. This allows for removing of
all resources not matching some tag key or value. In the example below, all EC2 instances without the `owner: me`
tag are deleted:

aws_instance:
- tags:
NOT(Owner): me
The flag `tagged: false` deletes all resources that have no tags. Contrary, resources with any tags can be deleted
with `tagged: true`:

aws_instance:
- tagged: true

##### 3) Delete By ID

You can narrow down on particular types of resources by filtering on their IDs.
You can narrow down on particular types of resources by filtering on based their IDs.

To see what the ID of a resource is (could be its name, ARN, a random number),
run AWSweeper in dry-run mode: `awsweeper --dry-run all.yml`. This way, nothing is deleted but
Expand Down
2 changes: 1 addition & 1 deletion command/wrapped_main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (
// WrappedMain is the actual main function that does not exit for acceptance testing purposes
func WrappedMain() int {
app := "awsweeper"
version := "v0.7.0"
version := "v0.8.0"

set := flag.NewFlagSet(app, 0)
versionFlag := set.Bool("version", false, "Show version")
Expand Down
80 changes: 52 additions & 28 deletions resource/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
"gopkg.in/yaml.v2"
)

// Filter represents the content of a yaml file that is used filter resources for deletion.
// Filter represents the content of a yaml file that is used to filter resources for deletion.
type Filter map[TerraformResourceType][]TypeFilter

// TypeFilter represents an entry in the yaml file to filter the resources of a particular resource type.
Expand Down Expand Up @@ -101,6 +101,7 @@ func (f TypeFilter) matchID(id string) bool {
return false
}

// MatchTagged filters resources with a non-empty or empty tag set.
func (f TypeFilter) MatchTagged(tags map[string]string) bool {
if f.Tagged == nil {
return true
Expand All @@ -117,19 +118,20 @@ func (f TypeFilter) MatchTagged(tags map[string]string) bool {
return false
}

// MatchesTags checks whether a resource's tags match the filter.
//
//The keys must match exactly, whereas the tag value is checked against a regex.
// MatchesTags checks whether a resource's tag set matches the filter.
func (f TypeFilter) MatchTags(tags map[string]string) bool {
if f.Tags == nil {
return f.matchIncludedTags(tags) && f.matchExcludedTags(tags)
}

// matchIncludedTags checks for tags that must be included in a resource's tag set.
func (f TypeFilter) matchIncludedTags(tags map[string]string) bool {
tagFilters := notNegatedTagFilterExpr(f.Tags)

if len(tagFilters) == 0 {
return true
}

for key, valueFilter := range f.Tags {
if isNegated(key) {
continue
}

for key, valueFilter := range tagFilters {
value, ok := tags[key]
if !ok {
return false
Expand All @@ -147,25 +149,15 @@ func (f TypeFilter) MatchTags(tags map[string]string) bool {
return true
}

func isNegated(s string) bool {
if strings.HasPrefix(s, "NOT(") && strings.HasSuffix(s, ")") {
return true
}

return false
}
// matchExcludedTags checks for tags that must not exist in a resource's tag set.
func (f TypeFilter) matchExcludedTags(tags map[string]string) bool {
tagFilters := negatedTagFilterExpr(f.Tags)

func (f TypeFilter) MatchNoTags(tags map[string]string) bool {
if f.Tags == nil {
if len(tagFilters) == 0 {
return true
}

for key, valueFilter := range f.Tags {
if !isNegated(key) {
continue
}
key = strings.TrimSuffix(strings.TrimPrefix(key, "NOT("), ")")

for key, valueFilter := range tagFilters {
value, ok := tags[key]
if !ok {
return true
Expand All @@ -183,6 +175,39 @@ func (f TypeFilter) MatchNoTags(tags map[string]string) bool {
return false
}

// notNegatedTagFilterExpr returns tag filter expressions where keys are not surrounded by NOT(...).
func notNegatedTagFilterExpr(tags map[string]StringFilter) map[string]StringFilter {
result := map[string]StringFilter{}

for key, value := range tags {
if !isNegatedTagKey(key) {
result[key] = value
}
}

return result
}

// notNegatedTagFilterExpr returns tag filter expressions where keys are surrounded by NOT(...).
func negatedTagFilterExpr(tags map[string]StringFilter) map[string]StringFilter {
result := map[string]StringFilter{}

for key, value := range tags {
if isNegatedTagKey(key) {
key = strings.TrimPrefix(key, "NOT(")
key = strings.TrimSuffix(key, ")")

result[key] = value
}
}

return result
}

func isNegatedTagKey(key string) bool {
return strings.HasPrefix(key, "NOT(") && strings.HasSuffix(key, ")")
}

func (f TypeFilter) matchCreated(creationTime *time.Time) bool {
if f.Created == nil {
return true
Expand All @@ -205,8 +230,8 @@ func (f TypeFilter) matchCreated(creationTime *time.Time) bool {
return createdAfter && createdBefore
}

// matches checks whether a resource matches the filter criteria.
func (f Filter) matches(r *Resource) bool {
// Match checks whether a resource matches the filter criteria.
func (f Filter) Match(r *Resource) bool {
resTypeFilters, found := f[r.Type]
if !found {
return false
Expand All @@ -219,7 +244,6 @@ func (f Filter) matches(r *Resource) bool {
for _, rtf := range resTypeFilters {
if rtf.MatchTagged(r.Tags) &&
rtf.MatchTags(r.Tags) &&
rtf.MatchNoTags(r.Tags) &&
rtf.matchID(r.ID) &&
rtf.matchCreated(r.Created) {
return true
Expand Down
4 changes: 2 additions & 2 deletions resource/filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ func TestTypeFilter_MatchTagged(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.filter.MatchTagged(tt.tags); got != tt.want {
t.Errorf("MatchTags() = %v, want %v", got, tt.want)
t.Errorf("MatchTagged() = %v, want %v", got, tt.want)
}
})
}
Expand Down Expand Up @@ -389,7 +389,7 @@ func TestTypeFilter_MatchNoTags(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.filter.MatchNoTags(tt.tags); got != tt.want {
if got := tt.filter.MatchTags(tt.tags); got != tt.want {
t.Errorf("MatchTags() = %v, want %v", got, tt.want)
}
})
Expand Down
12 changes: 6 additions & 6 deletions resource/select.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func (f Filter) defaultFilter(res Resources) []Resources {
result := Resources{}

for _, r := range res {
if f.matches(r) {
if f.Match(r) {
result = append(result, r)
}
}
Expand All @@ -49,7 +49,7 @@ func (f Filter) efsFileSystemFilter(res Resources, raw interface{}, c *AWS) []Re
resultMt := Resources{}

for _, r := range res {
if f.matches(&Resource{Type: r.Type, ID: *raw.([]*efs.FileSystemDescription)[0].Name}) {
if f.Match(&Resource{Type: r.Type, ID: *raw.([]*efs.FileSystemDescription)[0].Name}) {
res, err := c.DescribeMountTargets(&efs.DescribeMountTargetsInput{
FileSystemId: &r.ID,
})
Expand All @@ -74,7 +74,7 @@ func (f Filter) iamUserFilter(res Resources, c *AWS) []Resources {
resultUserPol := Resources{}

for _, r := range res {
if f.matches(r) {
if f.Match(r) {
// list inline policies, delete with "aws_iam_user_policy" delete routine
ups, err := c.ListUserPolicies(&iam.ListUserPoliciesInput{
UserName: &r.ID,
Expand Down Expand Up @@ -116,7 +116,7 @@ func (f Filter) iamPolicyFilter(res Resources, raw interface{}, c *AWS) []Resour
resultAtt := Resources{}

for i, r := range res {
if f.matches(r) {
if f.Match(r) {
es, err := c.ListEntitiesForPolicy(&iam.ListEntitiesForPolicyInput{
PolicyArn: &r.ID,
})
Expand Down Expand Up @@ -161,7 +161,7 @@ func (f Filter) kmsKeysFilter(res Resources, c *AWS) []Resources {
result := Resources{}

for _, r := range res {
if f.matches(r) {
if f.Match(r) {
req, res := c.DescribeKeyRequest(&kms.DescribeKeyInput{
KeyId: aws.String(r.ID),
})
Expand All @@ -184,7 +184,7 @@ func (f Filter) kmsKeyAliasFilter(res Resources) []Resources {
result := Resources{}

for _, r := range res {
if f.matches(r) && !strings.HasPrefix(r.ID, "alias/aws/") {
if f.Match(r) && !strings.HasPrefix(r.ID, "alias/aws/") {
result = append(result, r)
}
}
Expand Down

0 comments on commit be6ede7

Please sign in to comment.