Skip to content

Commit

Permalink
Refactor auto scaling group resource type
Browse files Browse the repository at this point in the history
  • Loading branch information
james03160927 committed Jul 21, 2023
1 parent 1b6398c commit ecfe9ad
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 210 deletions.
51 changes: 16 additions & 35 deletions aws/asg.go
Original file line number Diff line number Diff line change
@@ -1,63 +1,44 @@
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)
}
}

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 {
Expand All @@ -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{
Expand All @@ -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)
Expand All @@ -90,20 +71,20 @@ 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 {
logging.Logger.Errorf("[Failed] %s", err)
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
}
223 changes: 55 additions & 168 deletions aws/asg_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
12 changes: 6 additions & 6 deletions aws/asg_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
Loading

0 comments on commit ecfe9ad

Please sign in to comment.