Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor auto scaling group resource type #507

Merged
merged 1 commit into from
Jul 24, 2023
Merged
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
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