Skip to content
Open
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
21 changes: 6 additions & 15 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ func mainExitCode() int {
var logDebug bool
var outputType string
var parallel int
var profile string
var region string
var profiles []string
var regions []string
var timeout string
var version bool

Expand All @@ -46,8 +46,8 @@ func mainExitCode() int {
flags.StringVar(&outputType, "output", "string", "The type of output result (String, JSON or YAML)")
flags.BoolVar(&dryRun, "dry-run", false, "Don't delete anything, just show what would be deleted")
flags.BoolVar(&logDebug, "debug", false, "Enable debug logging")
flags.StringVarP(&profile, "profile", "p", "", "The AWS profile for the account to delete resources in")
flags.StringVarP(&region, "region", "r", "", "The region to delete resources in")
flags.StringSliceVarP(&profiles, "profiles", "p", []string{}, "The AWS profiles for the accounts to delete resources in")
flags.StringSliceVarP(&regions, "regions", "r", []string{}, "The regions to delete resources in")
flags.IntVar(&parallel, "parallel", 10, "Limit the number of concurrent delete operations")
flags.BoolVar(&version, "version", false, "Show application version")
flags.BoolVar(&force, "force", false, "Delete without asking for confirmation")
Expand Down Expand Up @@ -108,22 +108,13 @@ func mainExitCode() int {
return 1
}

var profiles []string
var regions []string

if profile != "" {
profiles = []string{profile}
} else {
if len(profiles) == 0 {
env, ok := os.LookupEnv("AWS_PROFILE")
if ok {
profiles = []string{env}
}
}

if region != "" {
regions = []string{region}
}

timeoutDuration, err := time.ParseDuration(timeout)
if err != nil {
log.WithError(err).Error("failed to parse timeout")
Expand Down Expand Up @@ -182,7 +173,7 @@ func mainExitCode() int {

resourcesCh := make(chan []terradozerRes.DestroyableResource, 1)
go func() {
resourcesCh <- resource.List(context.Background(), filter, clients, providers, outputType)
resourcesCh <- resource.List(ctx, filter, clients, providers, outputType)
}()
select {
case <-ctx.Done():
Expand Down
20 changes: 19 additions & 1 deletion pkg/resource/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type TypeFilter struct {
Tagged *bool `yaml:",omitempty"`
Tags map[string]StringFilter `yaml:",omitempty"`
Created *Created `yaml:",omitempty"`
Region *StringFilter `yaml:",omitempty"`
}

type StringMatcher interface {
Expand Down Expand Up @@ -237,6 +238,22 @@ func (f TypeFilter) matchCreated(creationTime *time.Time) bool {
return createdAfter && createdBefore
}

// MatchRegion checks whether a resource Region matches the filter.
func (f TypeFilter) MatchRegion(region string) bool {
if f.Region == nil {
return true
}

if ok, err := f.Region.matches(region); ok {
if err != nil {
log.WithError(err).Fatal("failed to match Region")
}
return true
}

return false
}

// Match checks whether a resource matches the filter criteria.
func (f Filter) Match(r terraform.Resource) bool {
resTypeFilters, found := f[r.Type]
Expand All @@ -252,7 +269,8 @@ func (f Filter) Match(r terraform.Resource) bool {
if rtf.MatchTagged(r.Tags) &&
rtf.MatchTags(r.Tags) &&
rtf.matchID(r.ID) &&
rtf.matchCreated(r.CreatedAt) {
rtf.matchCreated(r.CreatedAt) &&
rtf.MatchRegion(r.Region) {
return true
}
}
Expand Down
31 changes: 31 additions & 0 deletions pkg/resource/filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -403,3 +403,34 @@ func TestTypeFilter_MatchNoTags(t *testing.T) {
})
}
}

func TestTypeFilter_MatchRegion(t *testing.T) {
tests := []struct {
name string
filter resource.TypeFilter
region string
want bool
}{
{
name: "no region field in config",
filter: resource.TypeFilter{},
region: "us-west-2",
want: true,
},
{
name: "region in config matches",
filter: resource.TypeFilter{
Region: &resource.StringFilter{Pattern: "us-west-2", Negate: false},
},
region: "us-west-2",
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.filter.MatchRegion(tt.region); got != tt.want {
t.Errorf("MatchRegion() = %v, want %v", got, tt.want)
}
})
}
}
7 changes: 7 additions & 0 deletions pkg/resource/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,14 @@ func printString(res []terraform.Resource) {
fmt.Printf("\n\t---\n\tType: %s\n\tFound: %d\n\n", res[0].Type, len(res))

for _, r := range res {
profile := `N/A`
if r.Profile != "" {
profile = r.Profile
}

printStat := fmt.Sprintf("\t\tId:\t\t%s", r.ID)
printStat += fmt.Sprintf("\n\t\tProfile:\t%s", profile)
printStat += fmt.Sprintf("\n\t\tRegion:\t\t%s", r.Region)
if r.Tags != nil {
if len(r.Tags) > 0 {
var keys []string
Expand Down
29 changes: 29 additions & 0 deletions pkg/resource/select_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,35 @@ func TestYamlFilter_Apply_FilterByID(t *testing.T) {
assert.Equal(t, "select-this", result[0].ID)
}

func TestYamlFilter_Apply_FilterByRegion(t *testing.T) {
//given
f := &resource.Filter{
"aws_instance": {
{
Region: &resource.StringFilter{Pattern: "us-west-*"},
},
},
}

// when
res := []terraform.Resource{
{
Type: "aws_instance",
Region: "us-west-2",
},
{
Type: "aws_instance",
Region: "us-east-1",
},
}

result := f.Apply(res)

// then
require.Len(t, result, 1)
assert.Equal(t, "us-west-2", result[0].Region)
}

func TestYamlFilter_Apply_FilterByTag(t *testing.T) {
//given
f := &resource.Filter{
Expand Down
118 changes: 118 additions & 0 deletions test/acc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package test
import (
"fmt"
"os"
"regexp"
"runtime"
"testing"

Expand Down Expand Up @@ -121,3 +122,120 @@ func TestAcc_WrongPathToFilter(t *testing.T) {

fmt.Println(actualLogs)
}

func TestAcc_ProfilesAndRegions(t *testing.T) {
if testing.Short() {
t.Skip("Skipping acceptance test.")
}

testVars := InitEnv(t)

terraformDir := "./test-fixtures/multiple-profiles-and-regions"

terraformOptions := getTerraformOptions(terraformDir, testVars, map[string]interface{}{
"profile1": testVars.AWSProfile1,
"profile2": testVars.AWSProfile2,
"region1": testVars.AWSRegion1,
"region2": testVars.AWSRegion2,
})

defer terraform.Destroy(t, terraformOptions)

terraform.InitAndApply(t, terraformOptions)

vpcID1 := terraform.Output(t, terraformOptions, "id1")
vpcID2 := terraform.Output(t, terraformOptions, "id2")
vpcID3 := terraform.Output(t, terraformOptions, "id3")
vpcID4 := terraform.Output(t, terraformOptions, "id4")

writeConfigID(t, terraformDir, "aws_vpc", fmt.Sprintf("%s|%s|%s|%s", vpcID1, vpcID2, vpcID3, vpcID4))
defer os.Remove(terraformDir + "/config.yml")

tests := []struct {
name string
args []string
envs map[string]string
expectedLogs []string
expectedErrCode int
}{
{
name: "multiple profiles and regions via flag",
args: []string{
"-p", fmt.Sprintf("%s,%s", testVars.AWSProfile1, testVars.AWSProfile2),
"-r", fmt.Sprintf("%s,%s", testVars.AWSRegion1, testVars.AWSRegion2),
"--dry-run",
},
expectedLogs: []string{
fmt.Sprintf("Id:\\s+%[1]s\\s+Profile:\\s+%[2]s\\s+Region:\\s+%[3]s", vpcID1, testVars.AWSProfile1, testVars.AWSRegion1),
fmt.Sprintf("Id:\\s+%[1]s\\s+Profile:\\s+%[2]s\\s+Region:\\s+%[3]s", vpcID2, testVars.AWSProfile1, testVars.AWSRegion2),
fmt.Sprintf("Id:\\s+%[1]s\\s+Profile:\\s+%[2]s\\s+Region:\\s+%[3]s", vpcID3, testVars.AWSProfile2, testVars.AWSRegion1),
fmt.Sprintf("Id:\\s+%[1]s\\s+Profile:\\s+%[2]s\\s+Region:\\s+%[3]s", vpcID4, testVars.AWSProfile2, testVars.AWSRegion2),
"TOTAL NUMBER OF RESOURCES THAT WOULD BE DELETED: 4",
},
},
{
name: "profile via env, multiple regions via flag",
args: []string{
"-r", fmt.Sprintf("%s,%s", testVars.AWSRegion1, testVars.AWSRegion2),
"--dry-run",
},
envs: map[string]string{
"AWS_PROFILE": testVars.AWSProfile1,
},
expectedLogs: []string{
fmt.Sprintf("Id:\\s+%[1]s\\s+Profile:\\s+%[2]s\\s+Region:\\s+%[3]s", vpcID1, testVars.AWSProfile1, testVars.AWSRegion1),
fmt.Sprintf("Id:\\s+%[1]s\\s+Profile:\\s+%[2]s\\s+Region:\\s+%[3]s", vpcID2, testVars.AWSProfile1, testVars.AWSRegion2),
"TOTAL NUMBER OF RESOURCES THAT WOULD BE DELETED: 2",
},
},
{
name: "profile and region via env",
envs: map[string]string{
"AWS_PROFILE": testVars.AWSProfile1,
"AWS_DEFAULT_REGION": testVars.AWSRegion2,
},
args: []string{
"--dry-run",
},
expectedLogs: []string{
fmt.Sprintf("Id:\\s+%[1]s\\s+Profile:\\s+%[2]s\\s+Region:\\s+%[3]s", vpcID2, testVars.AWSProfile1, testVars.AWSRegion2),
"TOTAL NUMBER OF RESOURCES THAT WOULD BE DELETED: 1",
},
},
{
name: "profile via env, using default region from AWS config file",
envs: map[string]string{
"AWS_PROFILE": testVars.AWSProfile1,
},
args: []string{
"--dry-run",
},
expectedLogs: []string{
fmt.Sprintf("Id:\\s+%[1]s\\s+Profile:\\s+%[2]s\\s+Region:\\s+%[3]s", vpcID1, testVars.AWSProfile1, testVars.AWSRegion1),
"TOTAL NUMBER OF RESOURCES THAT WOULD BE DELETED: 1",
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
for k, v := range tc.envs {
t.Setenv(k, v)
}
logBuffer, err := runBinary(t, terraformDir, "", tc.args...)

if tc.expectedErrCode > 0 {
require.EqualError(t, err, "exit status 1")
} else {
require.NoError(t, err)
}

actualLogs := logBuffer.String()

for _, expectedLogEntry := range tc.expectedLogs {
assert.Regexp(t, regexp.MustCompile(expectedLogEntry), actualLogs)
}

fmt.Println(actualLogs)
})
}
}
Loading