Skip to content

Commit

Permalink
Multiple profiles and regions
Browse files Browse the repository at this point in the history
  • Loading branch information
jckuester committed Jul 11, 2022
1 parent 09952ce commit 0b516d5
Show file tree
Hide file tree
Showing 11 changed files with 367 additions and 36 deletions.
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

0 comments on commit 0b516d5

Please sign in to comment.