From ecfe9ad3edbe9f6bae7b83e1124ddb7a6023b943 Mon Sep 17 00:00:00 2001 From: James Kwon Date: Wed, 19 Jul 2023 18:16:03 -0400 Subject: [PATCH] Refactor auto scaling group resource type --- aws/asg.go | 51 ++++------- aws/asg_test.go | 223 ++++++++++++----------------------------------- aws/asg_types.go | 12 +-- aws/aws.go | 2 +- 4 files changed, 78 insertions(+), 210 deletions(-) diff --git a/aws/asg.go b/aws/asg.go index ba3778c1..8e0b2c54 100644 --- a/aws/asg.go +++ b/aws/asg.go @@ -1,30 +1,29 @@ package aws import ( - "github.com/gruntwork-io/cloud-nuke/telemetry" - commonTelemetry "github.com/gruntwork-io/go-commons/telemetry" - "time" - awsgo "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/autoscaling" "github.com/gruntwork-io/cloud-nuke/config" "github.com/gruntwork-io/cloud-nuke/logging" "github.com/gruntwork-io/cloud-nuke/report" + "github.com/gruntwork-io/cloud-nuke/telemetry" "github.com/gruntwork-io/go-commons/errors" + commonTelemetry "github.com/gruntwork-io/go-commons/telemetry" ) // Returns a formatted string of ASG Names -func getAllAutoScalingGroups(session *session.Session, region string, excludeAfter time.Time, configObj config.Config) ([]*string, error) { - svc := autoscaling.New(session) - result, err := svc.DescribeAutoScalingGroups(&autoscaling.DescribeAutoScalingGroupsInput{}) +func (ag ASGroups) getAll(configObj config.Config) ([]*string, error) { + result, err := ag.Client.DescribeAutoScalingGroups(&autoscaling.DescribeAutoScalingGroupsInput{}) if err != nil { return nil, errors.WithStackTrace(err) } var groupNames []*string for _, group := range result.AutoScalingGroups { - if shouldIncludeAutoScalingGroup(group, excludeAfter, configObj) { + if configObj.AutoScalingGroup.ShouldInclude(config.ResourceValue{ + Time: group.CreatedTime, + Name: group.AutoScalingGroupName, + }) { groupNames = append(groupNames, group.AutoScalingGroupName) } } @@ -32,32 +31,14 @@ func getAllAutoScalingGroups(session *session.Session, region string, excludeAft return groupNames, nil } -func shouldIncludeAutoScalingGroup(group *autoscaling.Group, excludeAfter time.Time, configObj config.Config) bool { - if group == nil { - return false - } - - if group.CreatedTime != nil && excludeAfter.Before(*group.CreatedTime) { - return false - } - - return config.ShouldInclude( - awsgo.StringValue(group.AutoScalingGroupName), - configObj.AutoScalingGroup.IncludeRule.NamesRegExp, - configObj.AutoScalingGroup.ExcludeRule.NamesRegExp, - ) -} - // Deletes all Auto Scaling Groups -func nukeAllAutoScalingGroups(session *session.Session, groupNames []*string) error { - svc := autoscaling.New(session) - +func (ag ASGroups) nukeAll(groupNames []*string) error { if len(groupNames) == 0 { - logging.Logger.Debugf("No Auto Scaling Groups to nuke in region %s", *session.Config.Region) + logging.Logger.Debugf("No Auto Scaling Groups to nuke in region %s", ag.Region) return nil } - logging.Logger.Debugf("Deleting all Auto Scaling Groups in region %s", *session.Config.Region) + logging.Logger.Debugf("Deleting all Auto Scaling Groups in region %s", ag.Region) var deletedGroupNames []*string for _, groupName := range groupNames { @@ -66,7 +47,7 @@ func nukeAllAutoScalingGroups(session *session.Session, groupNames []*string) er ForceDelete: awsgo.Bool(true), } - _, err := svc.DeleteAutoScalingGroup(params) + _, err := ag.Client.DeleteAutoScalingGroup(params) // Record status of this resource e := report.Entry{ @@ -81,7 +62,7 @@ func nukeAllAutoScalingGroups(session *session.Session, groupNames []*string) er telemetry.TrackEvent(commonTelemetry.EventContext{ EventName: "Error Nuking ASG", }, map[string]interface{}{ - "region": *session.Config.Region, + "region": ag.Region, }) } else { deletedGroupNames = append(deletedGroupNames, groupName) @@ -90,7 +71,7 @@ func nukeAllAutoScalingGroups(session *session.Session, groupNames []*string) er } if len(deletedGroupNames) > 0 { - err := svc.WaitUntilGroupNotExists(&autoscaling.DescribeAutoScalingGroupsInput{ + err := ag.Client.WaitUntilGroupNotExists(&autoscaling.DescribeAutoScalingGroupsInput{ AutoScalingGroupNames: deletedGroupNames, }) if err != nil { @@ -98,12 +79,12 @@ func nukeAllAutoScalingGroups(session *session.Session, groupNames []*string) er telemetry.TrackEvent(commonTelemetry.EventContext{ EventName: "Error Nuking ASG", }, map[string]interface{}{ - "region": *session.Config.Region, + "region": ag.Region, }) return errors.WithStackTrace(err) } } - logging.Logger.Debugf("[OK] %d Auto Scaling Group(s) deleted in %s", len(deletedGroupNames), *session.Config.Region) + logging.Logger.Debugf("[OK] %d Auto Scaling Group(s) deleted in %s", len(deletedGroupNames), ag.Region) return nil } diff --git a/aws/asg_test.go b/aws/asg_test.go index b0488ca2..7cd5e366 100644 --- a/aws/asg_test.go +++ b/aws/asg_test.go @@ -1,197 +1,84 @@ package aws import ( + "github.com/aws/aws-sdk-go/service/autoscaling/autoscalingiface" "github.com/gruntwork-io/cloud-nuke/telemetry" "regexp" "testing" "time" awsgo "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/autoscaling" "github.com/gruntwork-io/cloud-nuke/config" - "github.com/gruntwork-io/cloud-nuke/logging" - "github.com/gruntwork-io/cloud-nuke/util" - "github.com/gruntwork-io/go-commons/errors" "github.com/stretchr/testify/assert" ) -func createTestAutoScalingGroup(t *testing.T, session *session.Session, name string) { - svc := autoscaling.New(session) - instance := createTestEC2Instance(t, session, name, false) - - param := &autoscaling.CreateAutoScalingGroupInput{ - AutoScalingGroupName: &name, - InstanceId: instance.InstanceId, - MinSize: awsgo.Int64(1), - MaxSize: awsgo.Int64(2), - } - - _, err := svc.CreateAutoScalingGroup(param) - if err != nil { - assert.Failf(t, "Could not create test ASG", errors.WithStackTrace(err).Error()) - } - - err = svc.WaitUntilGroupExists(&autoscaling.DescribeAutoScalingGroupsInput{ - AutoScalingGroupNames: []*string{&name}, - }) - - if err != nil { - assert.Fail(t, errors.WithStackTrace(err).Error()) - } +type mockedASGroups struct { + autoscalingiface.AutoScalingAPI + DescribeAutoScalingGroupsResp autoscaling.DescribeAutoScalingGroupsOutput + DeleteAutoScalingGroupResp autoscaling.DeleteAutoScalingGroupOutput } -func TestListAutoScalingGroups(t *testing.T) { - telemetry.InitTelemetry("cloud-nuke", "") - t.Parallel() - - region, err := getRandomRegion() - if err != nil { - assert.Fail(t, errors.WithStackTrace(err).Error()) - } - session, err := session.NewSession(&awsgo.Config{ - Region: awsgo.String(region)}, - ) - - if err != nil { - assert.Fail(t, errors.WithStackTrace(err).Error()) - } - - uniqueTestID := "cloud-nuke-test-" + util.UniqueID() - createTestAutoScalingGroup(t, session, uniqueTestID) - // clean up after this test - defer nukeAllAutoScalingGroups(session, []*string{&uniqueTestID}) - defer nukeAllEc2Instances(session, findEC2InstancesByNameTag(t, session, uniqueTestID)) - - groupNames, err := getAllAutoScalingGroups(session, region, time.Now().Add(1*time.Hour*-1), config.Config{}) - if err != nil { - assert.Fail(t, "Unable to fetch list of Auto Scaling Groups") - } - - assert.NotContains(t, awsgo.StringValueSlice(groupNames), uniqueTestID) - - groupNames, err = getAllAutoScalingGroups(session, region, time.Now().Add(1*time.Hour), config.Config{}) - if err != nil { - assert.Fail(t, "Unable to fetch list of Auto Scaling Groups") - } - - assert.Contains(t, awsgo.StringValueSlice(groupNames), uniqueTestID) +func (m mockedASGroups) DescribeAutoScalingGroups(input *autoscaling.DescribeAutoScalingGroupsInput) (*autoscaling.DescribeAutoScalingGroupsOutput, error) { + return &m.DescribeAutoScalingGroupsResp, nil } -func TestNukeAutoScalingGroups(t *testing.T) { - telemetry.InitTelemetry("cloud-nuke", "") - t.Parallel() - - region, err := getRandomRegion() - if err != nil { - assert.Fail(t, errors.WithStackTrace(err).Error()) - } - session, err := session.NewSession(&awsgo.Config{ - Region: awsgo.String(region)}, - ) - svc := autoscaling.New(session) - - if err != nil { - assert.Fail(t, errors.WithStackTrace(err).Error()) - } - - uniqueTestID := "cloud-nuke-test-" + util.UniqueID() - createTestAutoScalingGroup(t, session, uniqueTestID) - - // clean up ec2 instance created by the above call - defer nukeAllEc2Instances(session, findEC2InstancesByNameTag(t, session, uniqueTestID)) - - _, err = svc.DescribeAutoScalingGroups(&autoscaling.DescribeAutoScalingGroupsInput{ - AutoScalingGroupNames: []*string{&uniqueTestID}, - }) - - if err != nil { - assert.Fail(t, errors.WithStackTrace(err).Error()) - } - - if err := nukeAllAutoScalingGroups(session, []*string{&uniqueTestID}); err != nil { - assert.Fail(t, errors.WithStackTrace(err).Error()) - } - - groupNames, err := getAllAutoScalingGroups(session, region, time.Now().Add(1*time.Hour), config.Config{}) - if err != nil { - assert.Fail(t, "Unable to fetch list of Auto Scaling Groups") - } +func (m mockedASGroups) DeleteAutoScalingGroup(input *autoscaling.DeleteAutoScalingGroupInput) (*autoscaling.DeleteAutoScalingGroupOutput, error) { + return &m.DeleteAutoScalingGroupResp, nil +} - assert.NotContains(t, awsgo.StringValueSlice(groupNames), uniqueTestID) +func (m mockedASGroups) WaitUntilGroupNotExists(input *autoscaling.DescribeAutoScalingGroupsInput) error { + return nil } -// Test config file filtering works as expected -func TestShouldIncludeAutoScalingGroup(t *testing.T) { +func TestAutoScalingGroupGetAll(t *testing.T) { telemetry.InitTelemetry("cloud-nuke", "") - mockAutoScalingGroup := &autoscaling.Group{ - AutoScalingGroupName: awsgo.String("cloud-nuke-test"), - CreatedTime: awsgo.Time(time.Now()), - } - - mockExpression, err := regexp.Compile("^cloud-nuke-*") - if err != nil { - logging.Logger.Fatalf("There was an error compiling regex expression %v", err) - } + t.Parallel() - mockExcludeConfig := config.Config{ + testName := "cloud-nuke-test" + now := time.Now() + ag := ASGroups{ + Client: mockedASGroups{ + DescribeAutoScalingGroupsResp: autoscaling.DescribeAutoScalingGroupsOutput{ + AutoScalingGroups: []*autoscaling.Group{{ + AutoScalingGroupName: awsgo.String(testName), + CreatedTime: awsgo.Time(now), + }}}}} + + // empty filter + groups, err := ag.getAll(config.Config{}) + assert.NoError(t, err) + assert.Contains(t, awsgo.StringValueSlice(groups), testName) + + // name filter + groups, err = ag.getAll(config.Config{ AutoScalingGroup: config.ResourceType{ ExcludeRule: config.FilterRule{ - NamesRegExp: []config.Expression{ - { - RE: *mockExpression, - }, - }, - }, - }, - } - - mockIncludeConfig := config.Config{ + NamesRegExp: []config.Expression{{ + RE: *regexp.MustCompile("^cloud-nuke-*"), + }}}}}) + assert.NoError(t, err) + assert.NotContains(t, awsgo.StringValueSlice(groups), testName) + + // time filter + groups, err = ag.getAll(config.Config{ AutoScalingGroup: config.ResourceType{ - IncludeRule: config.FilterRule{ - NamesRegExp: []config.Expression{ - { - RE: *mockExpression, - }, - }, - }, - }, - } + ExcludeRule: config.FilterRule{ + TimeAfter: awsgo.Time(now.Add(-1)), + }}}) + assert.NoError(t, err) + assert.NotContains(t, awsgo.StringValueSlice(groups), testName) +} + +func TestAutoScalingGroupNukeAll(t *testing.T) { + telemetry.InitTelemetry("cloud-nuke", "") + t.Parallel() - cases := []struct { - Name string - AutoScalingGroup *autoscaling.Group - Config config.Config - ExcludeAfter time.Time - Expected bool - }{ - { - Name: "ConfigExclude", - AutoScalingGroup: mockAutoScalingGroup, - Config: mockExcludeConfig, - ExcludeAfter: time.Now().Add(1 * time.Hour), - Expected: false, - }, - { - Name: "ConfigInclude", - AutoScalingGroup: mockAutoScalingGroup, - Config: mockIncludeConfig, - ExcludeAfter: time.Now().Add(1 * time.Hour), - Expected: true, - }, - { - Name: "NotOlderThan", - AutoScalingGroup: mockAutoScalingGroup, - Config: config.Config{}, - ExcludeAfter: time.Now().Add(1 * time.Hour * -1), - Expected: false, - }, - } + ag := ASGroups{ + Client: mockedASGroups{ + DeleteAutoScalingGroupResp: autoscaling.DeleteAutoScalingGroupOutput{}, + }} - for _, c := range cases { - t.Run(c.Name, func(t *testing.T) { - result := shouldIncludeAutoScalingGroup(c.AutoScalingGroup, c.ExcludeAfter, c.Config) - assert.Equal(t, c.Expected, result) - }) - } + err := ag.nukeAll([]*string{awsgo.String("cloud-nuke-test")}) + assert.NoError(t, err) } diff --git a/aws/asg_types.go b/aws/asg_types.go index 6b67d2bc..fbdd46d0 100644 --- a/aws/asg_types.go +++ b/aws/asg_types.go @@ -15,23 +15,23 @@ type ASGroups struct { } // ResourceName - the simple name of the aws resource -func (group ASGroups) ResourceName() string { +func (ag ASGroups) ResourceName() string { return "asg" } -func (group ASGroups) MaxBatchSize() int { +func (ag ASGroups) MaxBatchSize() int { // Tentative batch size to ensure AWS doesn't throttle return 49 } // ResourceIdentifiers - The group names of the auto scaling groups -func (group ASGroups) ResourceIdentifiers() []string { - return group.GroupNames +func (ag ASGroups) ResourceIdentifiers() []string { + return ag.GroupNames } // Nuke - nuke 'em all!!! -func (group ASGroups) Nuke(session *session.Session, identifiers []string) error { - if err := nukeAllAutoScalingGroups(session, awsgo.StringSlice(identifiers)); err != nil { +func (ag ASGroups) Nuke(session *session.Session, identifiers []string) error { + if err := ag.nukeAll(awsgo.StringSlice(identifiers)); err != nil { return errors.WithStackTrace(err) } diff --git a/aws/aws.go b/aws/aws.go index 2e9d9368..44993935 100644 --- a/aws/aws.go +++ b/aws/aws.go @@ -306,7 +306,7 @@ func GetAllResources(targetRegions []string, excludeAfter time.Time, resourceTyp } if IsNukeable(asGroups.ResourceName(), resourceTypes) { start := time.Now() - groupNames, err := getAllAutoScalingGroups(cloudNukeSession, region, excludeAfter, configObj) + groupNames, err := asGroups.getAll(configObj) if err != nil { ge := report.GeneralError{ Error: err,