From e84d0e447a064d819e2facfd20b513ef5dbebf33 Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Mon, 29 Jan 2018 11:25:26 +0100 Subject: [PATCH 01/38] Move EC2Instances type to ec2 specific types file --- aws/ec2_types.go | 7 ++++++- aws/types.go | 4 ---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/aws/ec2_types.go b/aws/ec2_types.go index cbf98742..16c202b7 100644 --- a/aws/ec2_types.go +++ b/aws/ec2_types.go @@ -6,7 +6,12 @@ import ( "github.com/gruntwork-io/gruntwork-cli/errors" ) -// Name - the simple name of the aws resource +// EC2Instances - represents all ec2 instances +type EC2Instances struct { + InstanceIds []string +} + +// ResourceName - the simple name of the aws resource func (instance EC2Instances) ResourceName() string { return "ec2" } diff --git a/aws/types.go b/aws/types.go index a8986dd7..85215fea 100644 --- a/aws/types.go +++ b/aws/types.go @@ -17,7 +17,3 @@ type AwsResources interface { type AwsRegionResource struct { Resources []AwsResources } - -type EC2Instances struct { - InstanceIds []string -} From 9649bae213a33606e16522ae1a65bee57e4dfd14 Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Mon, 29 Jan 2018 13:03:01 +0100 Subject: [PATCH 02/38] Add functionality to nuke Auto scaling groups --- aws/asg.go | 53 ++++++++++++++++++++++++++++++++++++++++++++++++ aws/asg_types.go | 31 ++++++++++++++++++++++++++++ aws/aws.go | 16 +++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 aws/asg.go create mode 100644 aws/asg_types.go diff --git a/aws/asg.go b/aws/asg.go new file mode 100644 index 00000000..4df75da9 --- /dev/null +++ b/aws/asg.go @@ -0,0 +1,53 @@ +package aws + +import ( + 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/aws-nuke/logging" + "github.com/gruntwork-io/gruntwork-cli/errors" +) + +// Returns a formatted string of ASG Names +func getAllAutoScalingGroups(session *session.Session, region string) ([]*string, error) { + svc := autoscaling.New(session) + result, err := svc.DescribeAutoScalingGroups(&autoscaling.DescribeAutoScalingGroupsInput{}) + if err != nil { + return nil, errors.WithStackTrace(err) + } + + var groupNames []*string + for _, group := range result.AutoScalingGroups { + groupNames = append(groupNames, group.AutoScalingGroupName) + } + + return groupNames, nil +} + +// Deletes all Auto Scaling Groups +func nukeAllAutoScalingGroups(session *session.Session, groupNames []*string) error { + svc := autoscaling.New(session) + + if len(groupNames) == 0 { + logging.Logger.Infof("No Auto Scaling Groups to nuke in region %s", *session.Config.Region) + return nil + } + + logging.Logger.Infof("Deleting all Auto Scaling Groups in region %s", *session.Config.Region) + + for _, groupName := range groupNames { + params := &autoscaling.DeleteAutoScalingGroupInput{ + AutoScalingGroupName: groupName, + ForceDelete: awsgo.Bool(true), + } + + _, err := svc.DeleteAutoScalingGroup(params) + if err != nil { + logging.Logger.Errorf("[Failed] %s", err) + return errors.WithStackTrace(err) + } + } + + logging.Logger.Infof("[OK] %d Auto Scaling Group(s) deleted in %s", len(groupNames), *session.Config.Region) + return nil +} diff --git a/aws/asg_types.go b/aws/asg_types.go new file mode 100644 index 00000000..3c4fa3ef --- /dev/null +++ b/aws/asg_types.go @@ -0,0 +1,31 @@ +package aws + +import ( + awsgo "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/gruntwork-io/gruntwork-cli/errors" +) + +// ASGroups - represents all ec2 instances +type ASGroups struct { + GroupNames []string +} + +// ResourceName - the simple name of the aws resource +func (group ASGroups) ResourceName() string { + return "asg" +} + +// ResourceIdentifiers - The group names of the auto scaling groups +func (group ASGroups) ResourceIdentifiers() []string { + return group.GroupNames +} + +// Nuke - nuke 'em all!!! +func (group ASGroups) Nuke(session *session.Session) error { + if err := nukeAllAutoScalingGroups(session, awsgo.StringSlice(group.GroupNames)); err != nil { + return errors.WithStackTrace(err) + } + + return nil +} diff --git a/aws/aws.go b/aws/aws.go index 5a4f4136..10980392 100644 --- a/aws/aws.go +++ b/aws/aws.go @@ -39,6 +39,20 @@ func GetAllResources() (*AwsAccountResources, error) { resourcesInRegion := AwsRegionResource{} + // ASG Names + groupNames, err := getAllAutoScalingGroups(session, region) + if err != nil { + return nil, errors.WithStackTrace(err) + } + + asGroups := ASGroups{ + GroupNames: awsgo.StringValueSlice(groupNames), + } + + resourcesInRegion.Resources = append(resourcesInRegion.Resources, asGroups) + // End ASG Names + + // EC2 Instances instanceIds, err := getAllEc2Instances(session, region) if err != nil { return nil, errors.WithStackTrace(err) @@ -49,6 +63,8 @@ func GetAllResources() (*AwsAccountResources, error) { } resourcesInRegion.Resources = append(resourcesInRegion.Resources, ec2Instances) + // End EC2 Instances + account.Resources[region] = resourcesInRegion } From feebc33a4e703df876616ea126b8e9770152c1a8 Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Mon, 29 Jan 2018 13:04:59 +0100 Subject: [PATCH 03/38] Include ASGs in list of supported AWS resources --- README.md | 1 + commands/cli.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f764d072..2bad600d 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ This repo contains a CLI tool to delete all AWS resources in an account. aws-nuk The currently supported functionality includes: * Deleting all unprotected EC2 instances in an AWS account +* Deleting all Auto scaling groups in an AWS account ### WARNING: THIS TOOL IS HIGHLY DESTRUCTIVE, ALL SUPPORTED RESOURCES WILL BE DELETED. ITS EFFECTS ARE IRREVERSIBLE AND SHOULD NEVER BE USED IN A PRODUCTION ENVIRONMENT diff --git a/commands/cli.go b/commands/cli.go index 617109b4..79cf6157 100644 --- a/commands/cli.go +++ b/commands/cli.go @@ -19,7 +19,7 @@ func CreateCli(version string) *cli.App { app.HelpName = app.Name app.Author = "Gruntwork " app.Version = version - app.Usage = "A CLI tool to cleanup AWS resources (EC2). THIS TOOL WILL COMPLETELY REMOVE ALL RESOURCES AND ITS EFFECTS ARE IRREVERSIBLE!!!" + app.Usage = "A CLI tool to cleanup AWS resources (ASG, EC2). THIS TOOL WILL COMPLETELY REMOVE ALL RESOURCES AND ITS EFFECTS ARE IRREVERSIBLE!!!" app.Action = errors.WithPanicHandling(awsNuke) return app From 56101fa0b73e774661001b484b06eeaa8c284dc6 Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Mon, 29 Jan 2018 16:36:44 +0100 Subject: [PATCH 04/38] Add tests for Auto Scaling Group nuking functionality --- aws/asg_test.go | 112 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 aws/asg_test.go diff --git a/aws/asg_test.go b/aws/asg_test.go new file mode 100644 index 00000000..88b35304 --- /dev/null +++ b/aws/asg_test.go @@ -0,0 +1,112 @@ +package aws + +import ( + "testing" + + 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/aws/aws-sdk-go/service/ec2" + "github.com/gruntwork-io/gruntwork-cli/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) + + // EC2 Instance must be in a running state before ASG can be created + err := ec2.New(session).WaitUntilInstanceRunning(&ec2.DescribeInstancesInput{ + Filters: []*ec2.Filter{ + &ec2.Filter{ + Name: awsgo.String("instance-id"), + Values: []*string{instance.InstanceId}, + }, + }, + }) + + if err != nil { + assert.Fail(t, errors.WithStackTrace(err).Error()) + } + + 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: %s", errors.WithStackTrace(err).Error()) + } + + err = svc.WaitUntilGroupExists(&autoscaling.DescribeAutoScalingGroupsInput{ + AutoScalingGroupNames: []*string{&name}, + }) + + if err != nil { + assert.Fail(t, errors.WithStackTrace(err).Error()) + } +} + +func TestListAutoScalingGroups(t *testing.T) { + session, err := session.NewSession(&awsgo.Config{ + Region: awsgo.String("us-west-2")}, + ) + + if err != nil { + assert.Fail(t, errors.WithStackTrace(err).Error()) + } + + groupName := "aws-nuke-test-" + uniqueID() + createTestAutoScalingGroup(t, session, groupName) + + groupNames, err := getAllAutoScalingGroups(session, "us-west-2") + if err != nil { + assert.Fail(t, "Unable to fetch list of Auto Scaling Groups") + } + + assert.Contains(t, awsgo.StringValueSlice(groupNames), groupName) +} + +func TestNukeAutoScalingGroups(t *testing.T) { + session, err := session.NewSession(&awsgo.Config{ + Region: awsgo.String("us-west-2")}, + ) + svc := autoscaling.New(session) + + if err != nil { + assert.Fail(t, errors.WithStackTrace(err).Error()) + } + + groupName := "aws-nuke-test-" + uniqueID() + createTestAutoScalingGroup(t, session, groupName) + + _, err = svc.DescribeAutoScalingGroups(&autoscaling.DescribeAutoScalingGroupsInput{ + AutoScalingGroupNames: []*string{&groupName}, + }) + + if err != nil { + assert.Fail(t, errors.WithStackTrace(err).Error()) + } + + if err := nukeAllAutoScalingGroups(session, []*string{&groupName}); err != nil { + assert.Fail(t, errors.WithStackTrace(err).Error()) + } + + err = svc.WaitUntilGroupNotExists(&autoscaling.DescribeAutoScalingGroupsInput{ + AutoScalingGroupNames: []*string{&groupName}, + }) + + if err != nil { + assert.Fail(t, errors.WithStackTrace(err).Error()) + } + + groupNames, err := getAllAutoScalingGroups(session, "us-west-2") + if err != nil { + assert.Fail(t, "Unable to fetch list of Auto Scaling Groups") + } + + assert.NotContains(t, awsgo.StringValueSlice(groupNames), groupName) +} From 4d683b195abd1ff605f9cb50c6090fc2bda261a6 Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Thu, 1 Feb 2018 13:48:23 +0100 Subject: [PATCH 05/38] add functionality to nuke elastic load balancers --- aws/asg_types.go | 2 +- aws/aws.go | 13 ++++++++++++ aws/elb.go | 51 ++++++++++++++++++++++++++++++++++++++++++++++++ aws/elb_types.go | 31 +++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 aws/elb.go create mode 100644 aws/elb_types.go diff --git a/aws/asg_types.go b/aws/asg_types.go index 3c4fa3ef..93416b93 100644 --- a/aws/asg_types.go +++ b/aws/asg_types.go @@ -6,7 +6,7 @@ import ( "github.com/gruntwork-io/gruntwork-cli/errors" ) -// ASGroups - represents all ec2 instances +// ASGroups - represents all auto scaling groups type ASGroups struct { GroupNames []string } diff --git a/aws/aws.go b/aws/aws.go index 10980392..f4d768f5 100644 --- a/aws/aws.go +++ b/aws/aws.go @@ -52,6 +52,19 @@ func GetAllResources() (*AwsAccountResources, error) { resourcesInRegion.Resources = append(resourcesInRegion.Resources, asGroups) // End ASG Names + // LoadBalancer Names + elbNames, err := getAllElbInstances(session, region) + if err != nil { + return nil, errors.WithStackTrace(err) + } + + loadBalancers := LoadBalancers{ + Names: awsgo.StringValueSlice(elbNames), + } + + resourcesInRegion.Resources = append(resourcesInRegion.Resources, loadBalancers) + // End LoadBalancer Names + // EC2 Instances instanceIds, err := getAllEc2Instances(session, region) if err != nil { diff --git a/aws/elb.go b/aws/elb.go new file mode 100644 index 00000000..9414860c --- /dev/null +++ b/aws/elb.go @@ -0,0 +1,51 @@ +package aws + +import ( + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/elb" + "github.com/gruntwork-io/aws-nuke/logging" + "github.com/gruntwork-io/gruntwork-cli/errors" +) + +// Returns a formatted string of ELB names +func getAllElbInstances(session *session.Session, region string) ([]*string, error) { + svc := elb.New(session) + result, err := svc.DescribeLoadBalancers(&elb.DescribeLoadBalancersInput{}) + if err != nil { + return nil, errors.WithStackTrace(err) + } + + var names []*string + for _, balancer := range result.LoadBalancerDescriptions { + names = append(names, balancer.LoadBalancerName) + } + + return names, nil +} + +// Deletes all Elastic Load Balancers +func nukeAllElbInstances(session *session.Session, names []*string) error { + svc := elb.New(session) + + if len(names) == 0 { + logging.Logger.Infof("No Elastic Load Balancers to nuke in region %s", *session.Config.Region) + return nil + } + + logging.Logger.Infof("Deleting all Elastic Load Balancers in region %s", *session.Config.Region) + + for _, name := range names { + params := &elb.DeleteLoadBalancerInput{ + LoadBalancerName: name, + } + + _, err := svc.DeleteLoadBalancer(params) + if err != nil { + logging.Logger.Errorf("[Failed] %s", err) + return errors.WithStackTrace(err) + } + } + + logging.Logger.Infof("[OK] %d Elastic Load Balancer(s) deleted in %s", len(names), *session.Config.Region) + return nil +} diff --git a/aws/elb_types.go b/aws/elb_types.go new file mode 100644 index 00000000..00a98082 --- /dev/null +++ b/aws/elb_types.go @@ -0,0 +1,31 @@ +package aws + +import ( + awsgo "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/gruntwork-io/gruntwork-cli/errors" +) + +// LoadBalancers - represents all load balancers +type LoadBalancers struct { + Names []string +} + +// ResourceName - the simple name of the aws resource +func (balancer LoadBalancers) ResourceName() string { + return "elb" +} + +// ResourceIdentifiers - The names of the load balancers +func (balancer LoadBalancers) ResourceIdentifiers() []string { + return balancer.Names +} + +// Nuke - nuke 'em all!!! +func (balancer LoadBalancers) Nuke(session *session.Session) error { + if err := nukeAllElbInstances(session, awsgo.StringSlice(balancer.Names)); err != nil { + return errors.WithStackTrace(err) + } + + return nil +} From 307d97b76282744fff78db976ea11877353a5db7 Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Thu, 1 Feb 2018 14:43:53 +0100 Subject: [PATCH 06/38] add functionality to nuke V2 elastic load balancers --- aws/aws.go | 13 ++++++++++++ aws/elbv2.go | 51 ++++++++++++++++++++++++++++++++++++++++++++++ aws/elbv2_types.go | 31 ++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 aws/elbv2.go create mode 100644 aws/elbv2_types.go diff --git a/aws/aws.go b/aws/aws.go index f4d768f5..f83e5a7c 100644 --- a/aws/aws.go +++ b/aws/aws.go @@ -65,6 +65,19 @@ func GetAllResources() (*AwsAccountResources, error) { resourcesInRegion.Resources = append(resourcesInRegion.Resources, loadBalancers) // End LoadBalancer Names + // LoadBalancerV2 Arns + elbv2Arns, err := getAllElbv2Instances(session, region) + if err != nil { + return nil, errors.WithStackTrace(err) + } + + loadBalancersV2 := LoadBalancersV2{ + Arns: awsgo.StringValueSlice(elbv2Arns), + } + + resourcesInRegion.Resources = append(resourcesInRegion.Resources, loadBalancersV2) + // End LoadBalancerV2 Arns + // EC2 Instances instanceIds, err := getAllEc2Instances(session, region) if err != nil { diff --git a/aws/elbv2.go b/aws/elbv2.go new file mode 100644 index 00000000..c0a6cc85 --- /dev/null +++ b/aws/elbv2.go @@ -0,0 +1,51 @@ +package aws + +import ( + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/elbv2" + "github.com/gruntwork-io/aws-nuke/logging" + "github.com/gruntwork-io/gruntwork-cli/errors" +) + +// Returns a formatted string of ELBv2 Arns +func getAllElbv2Instances(session *session.Session, region string) ([]*string, error) { + svc := elbv2.New(session) + result, err := svc.DescribeLoadBalancers(&elbv2.DescribeLoadBalancersInput{}) + if err != nil { + return nil, errors.WithStackTrace(err) + } + + var arns []*string + for _, balancer := range result.LoadBalancers { + arns = append(arns, balancer.LoadBalancerArn) + } + + return arns, nil +} + +// Deletes all Elastic Load Balancers +func nukeAllElbv2Instances(session *session.Session, arns []*string) error { + svc := elbv2.New(session) + + if len(arns) == 0 { + logging.Logger.Infof("No V2 Elastic Load Balancers to nuke in region %s", *session.Config.Region) + return nil + } + + logging.Logger.Infof("Deleting all V2 Elastic Load Balancers in region %s", *session.Config.Region) + + for _, arn := range arns { + params := &elbv2.DeleteLoadBalancerInput{ + LoadBalancerArn: arn, + } + + _, err := svc.DeleteLoadBalancer(params) + if err != nil { + logging.Logger.Errorf("[Failed] %s", err) + return errors.WithStackTrace(err) + } + } + + logging.Logger.Infof("[OK] %d V2 Elastic Load Balancer(s) deleted in %s", len(arns), *session.Config.Region) + return nil +} diff --git a/aws/elbv2_types.go b/aws/elbv2_types.go new file mode 100644 index 00000000..34c679d8 --- /dev/null +++ b/aws/elbv2_types.go @@ -0,0 +1,31 @@ +package aws + +import ( + awsgo "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/gruntwork-io/gruntwork-cli/errors" +) + +// LoadBalancersV2 - represents all load balancers +type LoadBalancersV2 struct { + Arns []string +} + +// ResourceName - the simple name of the aws resource +func (balancer LoadBalancersV2) ResourceName() string { + return "elbv2" +} + +// ResourceIdentifiers - The arns of the load balancers +func (balancer LoadBalancersV2) ResourceIdentifiers() []string { + return balancer.Arns +} + +// Nuke - nuke 'em all!!! +func (balancer LoadBalancersV2) Nuke(session *session.Session) error { + if err := nukeAllElbv2Instances(session, awsgo.StringSlice(balancer.Arns)); err != nil { + return errors.WithStackTrace(err) + } + + return nil +} From 8bf39d3317e5682a1fe92e4e343324defbf65d0e Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Thu, 1 Feb 2018 14:50:50 +0100 Subject: [PATCH 07/38] add information about deleting load balancers --- README.md | 3 ++- commands/cli.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2bad600d..9eaf6aca 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,9 @@ This repo contains a CLI tool to delete all AWS resources in an account. aws-nuk The currently supported functionality includes: -* Deleting all unprotected EC2 instances in an AWS account * Deleting all Auto scaling groups in an AWS account +* Deleting all Elastic Load Balancers (Classic and V2) in an AWS account +* Deleting all unprotected EC2 instances in an AWS account ### WARNING: THIS TOOL IS HIGHLY DESTRUCTIVE, ALL SUPPORTED RESOURCES WILL BE DELETED. ITS EFFECTS ARE IRREVERSIBLE AND SHOULD NEVER BE USED IN A PRODUCTION ENVIRONMENT diff --git a/commands/cli.go b/commands/cli.go index 79cf6157..74290fc6 100644 --- a/commands/cli.go +++ b/commands/cli.go @@ -19,7 +19,7 @@ func CreateCli(version string) *cli.App { app.HelpName = app.Name app.Author = "Gruntwork " app.Version = version - app.Usage = "A CLI tool to cleanup AWS resources (ASG, EC2). THIS TOOL WILL COMPLETELY REMOVE ALL RESOURCES AND ITS EFFECTS ARE IRREVERSIBLE!!!" + app.Usage = "A CLI tool to cleanup AWS resources (ASG, ELB, ELBv2, EC2). THIS TOOL WILL COMPLETELY REMOVE ALL RESOURCES AND ITS EFFECTS ARE IRREVERSIBLE!!!" app.Action = errors.WithPanicHandling(awsNuke) return app From 163170d2a6cb4292f95f58f1f8c7c9975b75c565 Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Thu, 1 Feb 2018 19:02:10 +0100 Subject: [PATCH 08/38] add functionality to nuke elastic block store volumes --- aws/aws.go | 13 ++++++++++++ aws/ebs.go | 52 ++++++++++++++++++++++++++++++++++++++++++++++++ aws/ebs_types.go | 31 +++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 aws/ebs.go create mode 100644 aws/ebs_types.go diff --git a/aws/aws.go b/aws/aws.go index f83e5a7c..36c55608 100644 --- a/aws/aws.go +++ b/aws/aws.go @@ -78,6 +78,19 @@ func GetAllResources() (*AwsAccountResources, error) { resourcesInRegion.Resources = append(resourcesInRegion.Resources, loadBalancersV2) // End LoadBalancerV2 Arns + // EBS Volumes + volumeIds, err := getAllEbsVolumes(session, region) + if err != nil { + return nil, errors.WithStackTrace(err) + } + + ebsVolumes := EBSVolumes{ + VolumeIds: awsgo.StringValueSlice(volumeIds), + } + + resourcesInRegion.Resources = append(resourcesInRegion.Resources, ebsVolumes) + // End EBS Volumes + // EC2 Instances instanceIds, err := getAllEc2Instances(session, region) if err != nil { diff --git a/aws/ebs.go b/aws/ebs.go new file mode 100644 index 00000000..2af3859e --- /dev/null +++ b/aws/ebs.go @@ -0,0 +1,52 @@ +package aws + +import ( + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/gruntwork-io/aws-nuke/logging" + "github.com/gruntwork-io/gruntwork-cli/errors" +) + +// Returns a formatted string of EBS volume ids +func getAllEbsVolumes(session *session.Session, region string) ([]*string, error) { + svc := ec2.New(session) + + result, err := svc.DescribeVolumes(&ec2.DescribeVolumesInput{}) + if err != nil { + return nil, errors.WithStackTrace(err) + } + + var volumeIds []*string + for _, volume := range result.Volumes { + volumeIds = append(volumeIds, volume.VolumeId) + } + + return volumeIds, nil +} + +// Deletes all EBS Volumes +func nukeAllEbsVolumes(session *session.Session, volumeIds []*string) error { + svc := ec2.New(session) + + if len(volumeIds) == 0 { + logging.Logger.Infof("No EBS volumes to nuke in region %s", *session.Config.Region) + return nil + } + + logging.Logger.Infof("Deleting all EBS volumes in region %s", *session.Config.Region) + + for _, volumeID := range volumeIds { + params := &ec2.DeleteVolumeInput{ + VolumeId: volumeID, + } + + _, err := svc.DeleteVolume(params) + if err != nil { + logging.Logger.Errorf("[Failed] %s", err) + return errors.WithStackTrace(err) + } + } + + logging.Logger.Infof("[OK] %d EBS volumes(s) terminated in %s", len(volumeIds), *session.Config.Region) + return nil +} diff --git a/aws/ebs_types.go b/aws/ebs_types.go new file mode 100644 index 00000000..7360d60b --- /dev/null +++ b/aws/ebs_types.go @@ -0,0 +1,31 @@ +package aws + +import ( + awsgo "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/gruntwork-io/gruntwork-cli/errors" +) + +// EBSVolumes - represents all ebs volumes +type EBSVolumes struct { + VolumeIds []string +} + +// ResourceName - the simple name of the aws resource +func (volume EBSVolumes) ResourceName() string { + return "ebs" +} + +// ResourceIdentifiers - The instance ids of the ec2 instances +func (volume EBSVolumes) ResourceIdentifiers() []string { + return volume.VolumeIds +} + +// Nuke - nuke 'em all!!! +func (volume EBSVolumes) Nuke(session *session.Session) error { + if err := nukeAllEbsVolumes(session, awsgo.StringSlice(volume.VolumeIds)); err != nil { + return errors.WithStackTrace(err) + } + + return nil +} From 7fb51a350f8273a0b7bd66647bb4af6027baec3a Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Thu, 1 Feb 2018 19:03:29 +0100 Subject: [PATCH 09/38] add information about deleting EBS volumes --- README.md | 1 + commands/cli.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9eaf6aca..9b879500 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ The currently supported functionality includes: * Deleting all Auto scaling groups in an AWS account * Deleting all Elastic Load Balancers (Classic and V2) in an AWS account +* Deleting all EBS Volumes in an AWS account * Deleting all unprotected EC2 instances in an AWS account ### WARNING: THIS TOOL IS HIGHLY DESTRUCTIVE, ALL SUPPORTED RESOURCES WILL BE DELETED. ITS EFFECTS ARE IRREVERSIBLE AND SHOULD NEVER BE USED IN A PRODUCTION ENVIRONMENT diff --git a/commands/cli.go b/commands/cli.go index 74290fc6..bc12e787 100644 --- a/commands/cli.go +++ b/commands/cli.go @@ -19,7 +19,7 @@ func CreateCli(version string) *cli.App { app.HelpName = app.Name app.Author = "Gruntwork " app.Version = version - app.Usage = "A CLI tool to cleanup AWS resources (ASG, ELB, ELBv2, EC2). THIS TOOL WILL COMPLETELY REMOVE ALL RESOURCES AND ITS EFFECTS ARE IRREVERSIBLE!!!" + app.Usage = "A CLI tool to cleanup AWS resources (ASG, ELB, ELBv2, EBS, EC2). THIS TOOL WILL COMPLETELY REMOVE ALL RESOURCES AND ITS EFFECTS ARE IRREVERSIBLE!!!" app.Action = errors.WithPanicHandling(awsNuke) return app From a29434ea0bf0015e9537701c2b1ad75f95e95cb3 Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Fri, 2 Feb 2018 15:50:19 +0100 Subject: [PATCH 10/38] add tests for nuking elb --- aws/elb_test.go | 89 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 aws/elb_test.go diff --git a/aws/elb_test.go b/aws/elb_test.go new file mode 100644 index 00000000..e96ba42e --- /dev/null +++ b/aws/elb_test.go @@ -0,0 +1,89 @@ +package aws + +import ( + "testing" + + awsgo "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/elb" + "github.com/gruntwork-io/gruntwork-cli/errors" + "github.com/stretchr/testify/assert" +) + +func createTestELB(t *testing.T, session *session.Session, name string) { + svc := elb.New(session) + + param := &elb.CreateLoadBalancerInput{ + AvailabilityZones: []*string{ + awsgo.String("us-west-2a"), + }, + LoadBalancerName: awsgo.String(name), + Listeners: []*elb.Listener{ + &elb.Listener{ + InstancePort: awsgo.Int64(80), + LoadBalancerPort: awsgo.Int64(80), + Protocol: awsgo.String("HTTP"), + }, + }, + } + + _, err := svc.CreateLoadBalancer(param) + if err != nil { + assert.Failf(t, "Could not create test ELB: %s", errors.WithStackTrace(err).Error()) + } +} + +func TestListELBs(t *testing.T) { + session, err := session.NewSession(&awsgo.Config{ + Region: awsgo.String("us-west-2")}, + ) + + if err != nil { + assert.Fail(t, errors.WithStackTrace(err).Error()) + } + + elbName := "aws-nuke-test-" + uniqueID() + createTestELB(t, session, elbName) + + elbNames, err := getAllElbInstances(session, "us-west-2") + if err != nil { + assert.Fail(t, "Unable to fetch list of Auto Scaling Groups") + } + + assert.Contains(t, awsgo.StringValueSlice(elbNames), elbName) +} + +func TestNukeELBs(t *testing.T) { + session, err := session.NewSession(&awsgo.Config{ + Region: awsgo.String("us-west-2")}, + ) + svc := elb.New(session) + + if err != nil { + assert.Fail(t, errors.WithStackTrace(err).Error()) + } + + elbName := "aws-nuke-test-" + uniqueID() + createTestELB(t, session, elbName) + + _, err = svc.DescribeLoadBalancers(&elb.DescribeLoadBalancersInput{ + LoadBalancerNames: []*string{ + awsgo.String(elbName), + }, + }) + + if err != nil { + assert.Fail(t, errors.WithStackTrace(err).Error()) + } + + if err := nukeAllElbInstances(session, []*string{&elbName}); err != nil { + assert.Fail(t, errors.WithStackTrace(err).Error()) + } + + elbNames, err := getAllElbInstances(session, "us-west-2") + if err != nil { + assert.Fail(t, "Unable to fetch list of ELBs") + } + + assert.NotContains(t, awsgo.StringValueSlice(elbNames), elbName) +} From dcdc88d033da477bcbe9a006d1dd409995f0aaa2 Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Fri, 2 Feb 2018 17:25:48 +0100 Subject: [PATCH 11/38] ensure ec2 instance is terminated before proceeding --- aws/aws.go | 24 ++++++++++++------------ aws/ec2.go | 14 ++++++++++++++ 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/aws/aws.go b/aws/aws.go index 36c55608..4ea476e7 100644 --- a/aws/aws.go +++ b/aws/aws.go @@ -78,31 +78,31 @@ func GetAllResources() (*AwsAccountResources, error) { resourcesInRegion.Resources = append(resourcesInRegion.Resources, loadBalancersV2) // End LoadBalancerV2 Arns - // EBS Volumes - volumeIds, err := getAllEbsVolumes(session, region) + // EC2 Instances + instanceIds, err := getAllEc2Instances(session, region) if err != nil { return nil, errors.WithStackTrace(err) } - ebsVolumes := EBSVolumes{ - VolumeIds: awsgo.StringValueSlice(volumeIds), + ec2Instances := EC2Instances{ + InstanceIds: awsgo.StringValueSlice(instanceIds), } - resourcesInRegion.Resources = append(resourcesInRegion.Resources, ebsVolumes) - // End EBS Volumes + resourcesInRegion.Resources = append(resourcesInRegion.Resources, ec2Instances) + // End EC2 Instances - // EC2 Instances - instanceIds, err := getAllEc2Instances(session, region) + // EBS Volumes + volumeIds, err := getAllEbsVolumes(session, region) if err != nil { return nil, errors.WithStackTrace(err) } - ec2Instances := EC2Instances{ - InstanceIds: awsgo.StringValueSlice(instanceIds), + ebsVolumes := EBSVolumes{ + VolumeIds: awsgo.StringValueSlice(volumeIds), } - resourcesInRegion.Resources = append(resourcesInRegion.Resources, ec2Instances) - // End EC2 Instances + resourcesInRegion.Resources = append(resourcesInRegion.Resources, ebsVolumes) + // End EBS Volumes account.Resources[region] = resourcesInRegion } diff --git a/aws/ec2.go b/aws/ec2.go index 87eecee2..a1db65e1 100644 --- a/aws/ec2.go +++ b/aws/ec2.go @@ -85,6 +85,20 @@ func nukeAllEc2Instances(session *session.Session, instanceIds []*string) error return errors.WithStackTrace(err) } + err = svc.WaitUntilInstanceTerminated(&ec2.DescribeInstancesInput{ + Filters: []*ec2.Filter{ + &ec2.Filter{ + Name: awsgo.String("instance-id"), + Values: instanceIds, + }, + }, + }) + + if err != nil { + logging.Logger.Errorf("[Failed] %s", err) + return errors.WithStackTrace(err) + } + logging.Logger.Infof("[OK] %d instance(s) terminated in %s", len(instanceIds), *session.Config.Region) return nil } From eb82cf8c58ad2e897f9c12307d8bd9fceac73efb Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Fri, 2 Feb 2018 18:18:23 +0100 Subject: [PATCH 12/38] add tests for nuking of EBS volumes --- aws/ebs_test.go | 117 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 aws/ebs_test.go diff --git a/aws/ebs_test.go b/aws/ebs_test.go new file mode 100644 index 00000000..a8182756 --- /dev/null +++ b/aws/ebs_test.go @@ -0,0 +1,117 @@ +package aws + +import ( + "testing" + + awsgo "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/gruntwork-io/gruntwork-cli/errors" + "github.com/stretchr/testify/assert" +) + +func createTestEBSVolume(t *testing.T, session *session.Session, name string) ec2.Volume { + svc := ec2.New(session) + volume, err := svc.CreateVolume(&ec2.CreateVolumeInput{ + AvailabilityZone: awsgo.String("us-west-2a"), + Size: awsgo.Int64(8), + }) + + if err != nil { + assert.Failf(t, "Could not create test EBS volume: %s", errors.WithStackTrace(err).Error()) + } + + err = svc.WaitUntilVolumeAvailable(&ec2.DescribeVolumesInput{ + VolumeIds: []*string{awsgo.String(*volume.VolumeId)}, + }) + + if err != nil { + assert.Fail(t, errors.WithStackTrace(err).Error()) + } + + // Add test tag to the created instance + _, err = svc.CreateTags(&ec2.CreateTagsInput{ + Resources: []*string{volume.VolumeId}, + Tags: []*ec2.Tag{ + { + Key: awsgo.String("Name"), + Value: awsgo.String(name), + }, + }, + }) + + if err != nil { + assert.Failf(t, "Could not tag EBS volume: %s", errors.WithStackTrace(err).Error()) + } + + return *volume +} + +func findEBSVolumesByNameTag(output *ec2.DescribeVolumesOutput, name string) []*string { + var volumeIds []*string + for _, volume := range output.Volumes { + // Retrive only IDs of instances with the unique test tag + for _, tag := range volume.Tags { + if *tag.Key == "Name" { + if *tag.Value == name { + volumeIds = append(volumeIds, volume.VolumeId) + } + } + } + } + + return volumeIds +} + +func TestListEBSVolumes(t *testing.T) { + session, err := session.NewSession(&awsgo.Config{ + Region: awsgo.String("us-west-2")}, + ) + + if err != nil { + assert.Fail(t, errors.WithStackTrace(err).Error()) + } + + uniqueTestID := "aws-nuke-test-" + uniqueID() + volume := createTestEBSVolume(t, session, uniqueTestID) + + volumeIds, err := getAllEbsVolumes(session, "us-west-2") + if err != nil { + assert.Fail(t, "Unable to fetch list of EBS Volumes") + } + + assert.Contains(t, awsgo.StringValueSlice(volumeIds), awsgo.StringValue(volume.VolumeId)) +} + +func TestNukeEBSVolumes(t *testing.T) { + session, err := session.NewSession(&awsgo.Config{ + Region: awsgo.String("us-west-2")}, + ) + + if err != nil { + assert.Fail(t, errors.WithStackTrace(err).Error()) + } + + uniqueTestID := "aws-nuke-test-" + uniqueID() + createTestEC2Instance(t, session, uniqueTestID) + + output, err := ec2.New(session).DescribeVolumes(&ec2.DescribeVolumesInput{}) + if err != nil { + assert.Fail(t, errors.WithStackTrace(err).Error()) + } + + volumeIds := findEBSVolumesByNameTag(output, uniqueTestID) + + if err := nukeAllEbsVolumes(session, volumeIds); err != nil { + assert.Fail(t, errors.WithStackTrace(err).Error()) + } + volumes, err := getAllEbsVolumes(session, "us-west-2") + + if err != nil { + assert.Fail(t, "Unable to fetch list of EC2 Instances") + } + + for _, volumeID := range volumeIds { + assert.NotContains(t, volumes, *volumeID) + } +} From 05f79e4689ca8c745a9673524dcf3495635f8008 Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Fri, 2 Feb 2018 18:31:54 +0100 Subject: [PATCH 13/38] ignore volume not found errors during nuking of ebs volumes --- aws/ebs.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/aws/ebs.go b/aws/ebs.go index 2af3859e..be70625d 100644 --- a/aws/ebs.go +++ b/aws/ebs.go @@ -1,6 +1,8 @@ package aws import ( + "strings" + "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/ec2" "github.com/gruntwork-io/aws-nuke/logging" @@ -42,8 +44,11 @@ func nukeAllEbsVolumes(session *session.Session, volumeIds []*string) error { _, err := svc.DeleteVolume(params) if err != nil { - logging.Logger.Errorf("[Failed] %s", err) - return errors.WithStackTrace(err) + // Ignore not found errors, some volumes are deleted along with EC2 Instances + if !strings.Contains(err.Error(), "InvalidVolume.NotFound") { + logging.Logger.Errorf("[Failed] %s", err) + return errors.WithStackTrace(err) + } } } From 99c91a52d85e7814c99df9acb55ce88ce8953f7d Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Mon, 5 Feb 2018 12:22:44 +0100 Subject: [PATCH 14/38] add test for nuking elbv2 --- aws/elbv2_test.go | 116 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 aws/elbv2_test.go diff --git a/aws/elbv2_test.go b/aws/elbv2_test.go new file mode 100644 index 00000000..bd1bb46b --- /dev/null +++ b/aws/elbv2_test.go @@ -0,0 +1,116 @@ +package aws + +import ( + "testing" + + awsgo "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/aws/aws-sdk-go/service/elbv2" + "github.com/gruntwork-io/gruntwork-cli/errors" + "github.com/stretchr/testify/assert" +) + +func createTestELBv2(t *testing.T, session *session.Session, name string) elbv2.LoadBalancer { + svc := elbv2.New(session) + + subnetOutput, err := ec2.New(session).DescribeSubnets(&ec2.DescribeSubnetsInput{}) + if err != nil { + assert.Fail(t, errors.WithStackTrace(err).Error()) + } + + if len(subnetOutput.Subnets) < 2 { + assert.Fail(t, "Needs at least 2 subnets to create ELBv2") + } + + subnet1 := *subnetOutput.Subnets[0] + subnet2 := *subnetOutput.Subnets[1] + + param := &elbv2.CreateLoadBalancerInput{ + Name: awsgo.String(name), + Subnets: []*string{ + subnet1.SubnetId, + subnet2.SubnetId, + }, + } + + result, err := svc.CreateLoadBalancer(param) + if err != nil { + assert.Failf(t, "Could not create test ELBv2: %s", errors.WithStackTrace(err).Error()) + } + + balancer := *result.LoadBalancers[0] + + err = svc.WaitUntilLoadBalancerAvailable(&elbv2.DescribeLoadBalancersInput{ + LoadBalancerArns: []*string{balancer.LoadBalancerArn}, + }) + + if err != nil { + assert.Fail(t, errors.WithStackTrace(err).Error()) + } + + return balancer +} + +func TestListELBv2(t *testing.T) { + session, err := session.NewSession(&awsgo.Config{ + Region: awsgo.String("us-west-2")}, + ) + + if err != nil { + assert.Fail(t, errors.WithStackTrace(err).Error()) + } + + elbName := "aws-nuke-test-" + uniqueID() + balancer := createTestELBv2(t, session, elbName) + + arns, err := getAllElbv2Instances(session, "us-west-2") + if err != nil { + assert.Fail(t, "Unable to fetch list of v2 ELBs") + } + + assert.Contains(t, awsgo.StringValueSlice(arns), awsgo.StringValue(balancer.LoadBalancerArn)) +} + +func TestNukeELBv2(t *testing.T) { + session, err := session.NewSession(&awsgo.Config{ + Region: awsgo.String("us-west-2")}, + ) + svc := elbv2.New(session) + + if err != nil { + assert.Fail(t, errors.WithStackTrace(err).Error()) + } + + elbName := "aws-nuke-test-" + uniqueID() + balancer := createTestELBv2(t, session, elbName) + + _, err = svc.DescribeLoadBalancers(&elbv2.DescribeLoadBalancersInput{ + LoadBalancerArns: []*string{ + balancer.LoadBalancerArn, + }, + }) + + if err != nil { + assert.Fail(t, errors.WithStackTrace(err).Error()) + } + + if err := nukeAllElbv2Instances(session, []*string{balancer.LoadBalancerArn}); err != nil { + assert.Fail(t, errors.WithStackTrace(err).Error()) + } + + err = svc.WaitUntilLoadBalancersDeleted(&elbv2.DescribeLoadBalancersInput{ + LoadBalancerArns: []*string{balancer.LoadBalancerArn}, + }) + + if err != nil { + assert.Fail(t, errors.WithStackTrace(err).Error()) + } + + arns, err := getAllElbv2Instances(session, "us-west-2") + if err != nil { + assert.Fail(t, "Unable to fetch list of v2 ELBs") + } + + assert.NotContains(t, awsgo.StringValueSlice(arns), awsgo.StringValue(balancer.LoadBalancerArn)) +} From 1d5c534817092cf62cd0d4de3e44f00c79614e00 Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Mon, 5 Feb 2018 15:51:19 +0100 Subject: [PATCH 15/38] ensure all ASGs have been nuked before nuke function returns --- aws/asg.go | 8 ++++++++ aws/asg_test.go | 8 -------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/aws/asg.go b/aws/asg.go index 4df75da9..ad7ccacb 100644 --- a/aws/asg.go +++ b/aws/asg.go @@ -48,6 +48,14 @@ func nukeAllAutoScalingGroups(session *session.Session, groupNames []*string) er } } + err := svc.WaitUntilGroupNotExists(&autoscaling.DescribeAutoScalingGroupsInput{ + AutoScalingGroupNames: groupNames, + }) + + if err != nil { + return errors.WithStackTrace(err) + } + logging.Logger.Infof("[OK] %d Auto Scaling Group(s) deleted in %s", len(groupNames), *session.Config.Region) return nil } diff --git a/aws/asg_test.go b/aws/asg_test.go index 88b35304..46686648 100644 --- a/aws/asg_test.go +++ b/aws/asg_test.go @@ -95,14 +95,6 @@ func TestNukeAutoScalingGroups(t *testing.T) { assert.Fail(t, errors.WithStackTrace(err).Error()) } - err = svc.WaitUntilGroupNotExists(&autoscaling.DescribeAutoScalingGroupsInput{ - AutoScalingGroupNames: []*string{&groupName}, - }) - - if err != nil { - assert.Fail(t, errors.WithStackTrace(err).Error()) - } - groupNames, err := getAllAutoScalingGroups(session, "us-west-2") if err != nil { assert.Fail(t, "Unable to fetch list of Auto Scaling Groups") From b51e6043497d91e4a3fa8521036f18d4de78f200 Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Mon, 5 Feb 2018 16:06:55 +0100 Subject: [PATCH 16/38] update ebs test to guard against nil pointer --- aws/ebs_test.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/aws/ebs_test.go b/aws/ebs_test.go index a8182756..83998f1a 100644 --- a/aws/ebs_test.go +++ b/aws/ebs_test.go @@ -52,10 +52,8 @@ func findEBSVolumesByNameTag(output *ec2.DescribeVolumesOutput, name string) []* for _, volume := range output.Volumes { // Retrive only IDs of instances with the unique test tag for _, tag := range volume.Tags { - if *tag.Key == "Name" { - if *tag.Value == name { - volumeIds = append(volumeIds, volume.VolumeId) - } + if awsgo.StringValue(tag.Key) == "Name" && awsgo.StringValue(tag.Value) == name { + volumeIds = append(volumeIds, volume.VolumeId) } } } From c95bad099a3a2d82b51468598207f978c60b043b Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Mon, 5 Feb 2018 16:10:31 +0100 Subject: [PATCH 17/38] ensure elbv2 load balancer is successfully created --- aws/elbv2_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/aws/elbv2_test.go b/aws/elbv2_test.go index bd1bb46b..a37ee312 100644 --- a/aws/elbv2_test.go +++ b/aws/elbv2_test.go @@ -35,10 +35,15 @@ func createTestELBv2(t *testing.T, session *session.Session, name string) elbv2. } result, err := svc.CreateLoadBalancer(param) + if err != nil { assert.Failf(t, "Could not create test ELBv2: %s", errors.WithStackTrace(err).Error()) } + if len(result.LoadBalancers) == 0 { + assert.Failf(t, "Could not create test ELBv2: %s", errors.WithStackTrace(err).Error()) + } + balancer := *result.LoadBalancers[0] err = svc.WaitUntilLoadBalancerAvailable(&elbv2.DescribeLoadBalancersInput{ From fa9ad56bbd0787f4e7fc991d63b11167dad3fa27 Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Mon, 5 Feb 2018 16:14:19 +0100 Subject: [PATCH 18/38] ensure ebs volume is deleted before returning from nuke method --- aws/ebs.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/aws/ebs.go b/aws/ebs.go index be70625d..71548f41 100644 --- a/aws/ebs.go +++ b/aws/ebs.go @@ -49,9 +49,19 @@ func nukeAllEbsVolumes(session *session.Session, volumeIds []*string) error { logging.Logger.Errorf("[Failed] %s", err) return errors.WithStackTrace(err) } + + logging.Logger.Infof("EBS volume %s has already been deleted", *volumeID) } } + err := svc.WaitUntilVolumeDeleted(&ec2.DescribeVolumesInput{ + VolumeIds: volumeIds, + }) + + if err != nil { + return errors.WithStackTrace(err) + } + logging.Logger.Infof("[OK] %d EBS volumes(s) terminated in %s", len(volumeIds), *session.Config.Region) return nil } From d7cd7ec00fd1b4f05cdb8434f7a46048da630ccc Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Mon, 5 Feb 2018 16:18:38 +0100 Subject: [PATCH 19/38] ensure elbv2 is deleted before returning from nuke function --- aws/elbv2.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/aws/elbv2.go b/aws/elbv2.go index c0a6cc85..2159897c 100644 --- a/aws/elbv2.go +++ b/aws/elbv2.go @@ -46,6 +46,14 @@ func nukeAllElbv2Instances(session *session.Session, arns []*string) error { } } + err := svc.WaitUntilLoadBalancersDeleted(&elbv2.DescribeLoadBalancersInput{ + LoadBalancerArns: arns, + }) + + if err != nil { + return errors.WithStackTrace(err) + } + logging.Logger.Infof("[OK] %d V2 Elastic Load Balancer(s) deleted in %s", len(arns), *session.Config.Region) return nil } From e8d7c5a37bd46b0f7e6b4c86f3ea20188e12bda9 Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Mon, 5 Feb 2018 16:26:27 +0100 Subject: [PATCH 20/38] Run all tests in parallel --- aws/asg_test.go | 2 ++ aws/ebs_test.go | 2 ++ aws/ec2_test.go | 2 ++ aws/elb_test.go | 2 ++ aws/elbv2_test.go | 2 ++ 5 files changed, 10 insertions(+) diff --git a/aws/asg_test.go b/aws/asg_test.go index 46686648..5cee6974 100644 --- a/aws/asg_test.go +++ b/aws/asg_test.go @@ -51,6 +51,7 @@ func createTestAutoScalingGroup(t *testing.T, session *session.Session, name str } func TestListAutoScalingGroups(t *testing.T) { + t.Parallel() session, err := session.NewSession(&awsgo.Config{ Region: awsgo.String("us-west-2")}, ) @@ -71,6 +72,7 @@ func TestListAutoScalingGroups(t *testing.T) { } func TestNukeAutoScalingGroups(t *testing.T) { + t.Parallel() session, err := session.NewSession(&awsgo.Config{ Region: awsgo.String("us-west-2")}, ) diff --git a/aws/ebs_test.go b/aws/ebs_test.go index 83998f1a..9d1ada1e 100644 --- a/aws/ebs_test.go +++ b/aws/ebs_test.go @@ -62,6 +62,7 @@ func findEBSVolumesByNameTag(output *ec2.DescribeVolumesOutput, name string) []* } func TestListEBSVolumes(t *testing.T) { + t.Parallel() session, err := session.NewSession(&awsgo.Config{ Region: awsgo.String("us-west-2")}, ) @@ -82,6 +83,7 @@ func TestListEBSVolumes(t *testing.T) { } func TestNukeEBSVolumes(t *testing.T) { + t.Parallel() session, err := session.NewSession(&awsgo.Config{ Region: awsgo.String("us-west-2")}, ) diff --git a/aws/ec2_test.go b/aws/ec2_test.go index fd2cdb4d..c7c15040 100644 --- a/aws/ec2_test.go +++ b/aws/ec2_test.go @@ -99,6 +99,7 @@ func findEC2InstancesByNameTag(output *ec2.DescribeInstancesOutput, name string) } func TestListInstances(t *testing.T) { + t.Parallel() session, err := session.NewSession(&awsgo.Config{ Region: awsgo.String("us-west-2")}, ) @@ -119,6 +120,7 @@ func TestListInstances(t *testing.T) { } func TestNukeInstances(t *testing.T) { + t.Parallel() session, err := session.NewSession(&awsgo.Config{ Region: awsgo.String("us-west-2")}, ) diff --git a/aws/elb_test.go b/aws/elb_test.go index e96ba42e..a1ad2597 100644 --- a/aws/elb_test.go +++ b/aws/elb_test.go @@ -34,6 +34,7 @@ func createTestELB(t *testing.T, session *session.Session, name string) { } func TestListELBs(t *testing.T) { + t.Parallel() session, err := session.NewSession(&awsgo.Config{ Region: awsgo.String("us-west-2")}, ) @@ -54,6 +55,7 @@ func TestListELBs(t *testing.T) { } func TestNukeELBs(t *testing.T) { + t.Parallel() session, err := session.NewSession(&awsgo.Config{ Region: awsgo.String("us-west-2")}, ) diff --git a/aws/elbv2_test.go b/aws/elbv2_test.go index a37ee312..50bdd484 100644 --- a/aws/elbv2_test.go +++ b/aws/elbv2_test.go @@ -58,6 +58,7 @@ func createTestELBv2(t *testing.T, session *session.Session, name string) elbv2. } func TestListELBv2(t *testing.T) { + t.Parallel() session, err := session.NewSession(&awsgo.Config{ Region: awsgo.String("us-west-2")}, ) @@ -78,6 +79,7 @@ func TestListELBv2(t *testing.T) { } func TestNukeELBv2(t *testing.T) { + t.Parallel() session, err := session.NewSession(&awsgo.Config{ Region: awsgo.String("us-west-2")}, ) From 69b32f31f10d13b879950cdbecbb9d8ed6089853 Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Mon, 5 Feb 2018 19:23:40 +0100 Subject: [PATCH 21/38] add funtion to return a random region and ignore reserved regions --- aws/aws.go | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/aws/aws.go b/aws/aws.go index 4ea476e7..a2be3973 100644 --- a/aws/aws.go +++ b/aws/aws.go @@ -1,27 +1,45 @@ package aws import ( + "math/rand" + "time" + awsgo "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/endpoints" "github.com/aws/aws-sdk-go/aws/session" + "github.com/gruntwork-io/gruntwork-cli/collections" "github.com/gruntwork-io/gruntwork-cli/errors" ) // Returns a list of all AWS regions func getAllRegions() []string { + // chinese and government regions are not accessible with regular accounts + reservedRegions := []string{ + "cn-north-1", "cn-northwest-1", "us-gov-west-1", + } + resolver := endpoints.DefaultResolver() partitions := resolver.(endpoints.EnumPartitions).Partitions() var regions []string for _, p := range partitions { for id := range p.Regions() { - regions = append(regions, id) + if !collections.ListContainsElement(reservedRegions, id) { + regions = append(regions, id) + } } } return regions } +func getRandomRegion() string { + allRegions := getAllRegions() + rand.Seed(time.Now().UnixNano()) + randIndex := rand.Intn(len(allRegions)) + return allRegions[randIndex] +} + // GetAllResources - Lists all aws resources func GetAllResources() (*AwsAccountResources, error) { account := AwsAccountResources{ From ab693b0ac89b8c4d8c04ee5fd4f47e6dc6fca664 Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Mon, 5 Feb 2018 19:24:38 +0100 Subject: [PATCH 22/38] use random regions for tests --- aws/asg_test.go | 12 ++++++++---- aws/ebs_test.go | 14 +++++++++----- aws/ec2_test.go | 12 ++++++++---- aws/elb_test.go | 14 +++++++++----- aws/elbv2_test.go | 12 ++++++++---- 5 files changed, 42 insertions(+), 22 deletions(-) diff --git a/aws/asg_test.go b/aws/asg_test.go index 5cee6974..4d4c4181 100644 --- a/aws/asg_test.go +++ b/aws/asg_test.go @@ -52,8 +52,10 @@ func createTestAutoScalingGroup(t *testing.T, session *session.Session, name str func TestListAutoScalingGroups(t *testing.T) { t.Parallel() + + region := getRandomRegion() session, err := session.NewSession(&awsgo.Config{ - Region: awsgo.String("us-west-2")}, + Region: awsgo.String(region)}, ) if err != nil { @@ -63,7 +65,7 @@ func TestListAutoScalingGroups(t *testing.T) { groupName := "aws-nuke-test-" + uniqueID() createTestAutoScalingGroup(t, session, groupName) - groupNames, err := getAllAutoScalingGroups(session, "us-west-2") + groupNames, err := getAllAutoScalingGroups(session, region) if err != nil { assert.Fail(t, "Unable to fetch list of Auto Scaling Groups") } @@ -73,8 +75,10 @@ func TestListAutoScalingGroups(t *testing.T) { func TestNukeAutoScalingGroups(t *testing.T) { t.Parallel() + + region := getRandomRegion() session, err := session.NewSession(&awsgo.Config{ - Region: awsgo.String("us-west-2")}, + Region: awsgo.String(region)}, ) svc := autoscaling.New(session) @@ -97,7 +101,7 @@ func TestNukeAutoScalingGroups(t *testing.T) { assert.Fail(t, errors.WithStackTrace(err).Error()) } - groupNames, err := getAllAutoScalingGroups(session, "us-west-2") + groupNames, err := getAllAutoScalingGroups(session, region) if err != nil { assert.Fail(t, "Unable to fetch list of Auto Scaling Groups") } diff --git a/aws/ebs_test.go b/aws/ebs_test.go index 9d1ada1e..370f179f 100644 --- a/aws/ebs_test.go +++ b/aws/ebs_test.go @@ -13,7 +13,7 @@ import ( func createTestEBSVolume(t *testing.T, session *session.Session, name string) ec2.Volume { svc := ec2.New(session) volume, err := svc.CreateVolume(&ec2.CreateVolumeInput{ - AvailabilityZone: awsgo.String("us-west-2a"), + AvailabilityZone: awsgo.String(awsgo.StringValue(session.Config.Region) + "a"), Size: awsgo.Int64(8), }) @@ -63,8 +63,10 @@ func findEBSVolumesByNameTag(output *ec2.DescribeVolumesOutput, name string) []* func TestListEBSVolumes(t *testing.T) { t.Parallel() + + region := getRandomRegion() session, err := session.NewSession(&awsgo.Config{ - Region: awsgo.String("us-west-2")}, + Region: awsgo.String(region)}, ) if err != nil { @@ -74,7 +76,7 @@ func TestListEBSVolumes(t *testing.T) { uniqueTestID := "aws-nuke-test-" + uniqueID() volume := createTestEBSVolume(t, session, uniqueTestID) - volumeIds, err := getAllEbsVolumes(session, "us-west-2") + volumeIds, err := getAllEbsVolumes(session, region) if err != nil { assert.Fail(t, "Unable to fetch list of EBS Volumes") } @@ -84,8 +86,10 @@ func TestListEBSVolumes(t *testing.T) { func TestNukeEBSVolumes(t *testing.T) { t.Parallel() + + region := getRandomRegion() session, err := session.NewSession(&awsgo.Config{ - Region: awsgo.String("us-west-2")}, + Region: awsgo.String(region)}, ) if err != nil { @@ -105,7 +109,7 @@ func TestNukeEBSVolumes(t *testing.T) { if err := nukeAllEbsVolumes(session, volumeIds); err != nil { assert.Fail(t, errors.WithStackTrace(err).Error()) } - volumes, err := getAllEbsVolumes(session, "us-west-2") + volumes, err := getAllEbsVolumes(session, region) if err != nil { assert.Fail(t, "Unable to fetch list of EC2 Instances") diff --git a/aws/ec2_test.go b/aws/ec2_test.go index c7c15040..c1da3eaa 100644 --- a/aws/ec2_test.go +++ b/aws/ec2_test.go @@ -100,8 +100,10 @@ func findEC2InstancesByNameTag(output *ec2.DescribeInstancesOutput, name string) func TestListInstances(t *testing.T) { t.Parallel() + + region := getRandomRegion() session, err := session.NewSession(&awsgo.Config{ - Region: awsgo.String("us-west-2")}, + Region: awsgo.String(region)}, ) if err != nil { @@ -110,7 +112,7 @@ func TestListInstances(t *testing.T) { uniqueTestID := "aws-nuke-test-" + uniqueID() instance := createTestEC2Instance(t, session, uniqueTestID) - instanceIds, err := getAllEc2Instances(session, "us-west-2") + instanceIds, err := getAllEc2Instances(session, region) if err != nil { assert.Fail(t, "Unable to fetch list of EC2 Instances") @@ -121,8 +123,10 @@ func TestListInstances(t *testing.T) { func TestNukeInstances(t *testing.T) { t.Parallel() + + region := getRandomRegion() session, err := session.NewSession(&awsgo.Config{ - Region: awsgo.String("us-west-2")}, + Region: awsgo.String(region)}, ) if err != nil { @@ -142,7 +146,7 @@ func TestNukeInstances(t *testing.T) { if err := nukeAllEc2Instances(session, instanceIds); err != nil { assert.Fail(t, errors.WithStackTrace(err).Error()) } - instances, err := getAllEc2Instances(session, "us-west-2") + instances, err := getAllEc2Instances(session, region) if err != nil { assert.Fail(t, "Unable to fetch list of EC2 Instances") diff --git a/aws/elb_test.go b/aws/elb_test.go index a1ad2597..441f420e 100644 --- a/aws/elb_test.go +++ b/aws/elb_test.go @@ -15,7 +15,7 @@ func createTestELB(t *testing.T, session *session.Session, name string) { param := &elb.CreateLoadBalancerInput{ AvailabilityZones: []*string{ - awsgo.String("us-west-2a"), + awsgo.String(awsgo.StringValue(session.Config.Region) + "a"), }, LoadBalancerName: awsgo.String(name), Listeners: []*elb.Listener{ @@ -35,8 +35,10 @@ func createTestELB(t *testing.T, session *session.Session, name string) { func TestListELBs(t *testing.T) { t.Parallel() + + region := getRandomRegion() session, err := session.NewSession(&awsgo.Config{ - Region: awsgo.String("us-west-2")}, + Region: awsgo.String(region)}, ) if err != nil { @@ -46,7 +48,7 @@ func TestListELBs(t *testing.T) { elbName := "aws-nuke-test-" + uniqueID() createTestELB(t, session, elbName) - elbNames, err := getAllElbInstances(session, "us-west-2") + elbNames, err := getAllElbInstances(session, region) if err != nil { assert.Fail(t, "Unable to fetch list of Auto Scaling Groups") } @@ -56,8 +58,10 @@ func TestListELBs(t *testing.T) { func TestNukeELBs(t *testing.T) { t.Parallel() + + region := getRandomRegion() session, err := session.NewSession(&awsgo.Config{ - Region: awsgo.String("us-west-2")}, + Region: awsgo.String(region)}, ) svc := elb.New(session) @@ -82,7 +86,7 @@ func TestNukeELBs(t *testing.T) { assert.Fail(t, errors.WithStackTrace(err).Error()) } - elbNames, err := getAllElbInstances(session, "us-west-2") + elbNames, err := getAllElbInstances(session, region) if err != nil { assert.Fail(t, "Unable to fetch list of ELBs") } diff --git a/aws/elbv2_test.go b/aws/elbv2_test.go index 50bdd484..f1387915 100644 --- a/aws/elbv2_test.go +++ b/aws/elbv2_test.go @@ -59,8 +59,10 @@ func createTestELBv2(t *testing.T, session *session.Session, name string) elbv2. func TestListELBv2(t *testing.T) { t.Parallel() + + region := getRandomRegion() session, err := session.NewSession(&awsgo.Config{ - Region: awsgo.String("us-west-2")}, + Region: awsgo.String(region)}, ) if err != nil { @@ -70,7 +72,7 @@ func TestListELBv2(t *testing.T) { elbName := "aws-nuke-test-" + uniqueID() balancer := createTestELBv2(t, session, elbName) - arns, err := getAllElbv2Instances(session, "us-west-2") + arns, err := getAllElbv2Instances(session, region) if err != nil { assert.Fail(t, "Unable to fetch list of v2 ELBs") } @@ -80,8 +82,10 @@ func TestListELBv2(t *testing.T) { func TestNukeELBv2(t *testing.T) { t.Parallel() + + region := getRandomRegion() session, err := session.NewSession(&awsgo.Config{ - Region: awsgo.String("us-west-2")}, + Region: awsgo.String(region)}, ) svc := elbv2.New(session) @@ -114,7 +118,7 @@ func TestNukeELBv2(t *testing.T) { assert.Fail(t, errors.WithStackTrace(err).Error()) } - arns, err := getAllElbv2Instances(session, "us-west-2") + arns, err := getAllElbv2Instances(session, region) if err != nil { assert.Fail(t, "Unable to fetch list of v2 ELBs") } From a4048ef9980681d913ced67f9d438cfd54322fe7 Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Tue, 6 Feb 2018 12:17:48 +0100 Subject: [PATCH 23/38] add function to return a linux ami for a specific region --- aws/ec2_test.go | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/aws/ec2_test.go b/aws/ec2_test.go index c1da3eaa..0361ddf2 100644 --- a/aws/ec2_test.go +++ b/aws/ec2_test.go @@ -31,11 +31,48 @@ func uniqueID() string { return out.String() } +func getLinuxAmiIDForRegion(region string) string { + switch region { + case "us-east-1": + return "ami-97785bed" + case "us-east-2": + return "ami-f63b1193" + case "us-west-1": + return "ami-824c4ee2" + case "us-west-2": + return "ami-f2d3638a" + case "ca-central-1": + return "ami-a954d1cd" + case "eu-west-1": + return "ami-d834aba1" + case "eu-west-2": + return "ami-403e2524" + case "eu-west-3": + return "ami-8ee056f3" + case "eu-central-1": + return "ami-5652ce39" + case "ap-northeast-1": + return "ami-ceafcba8" + case "ap-northeast-2": + return "ami-863090e8" + case "ap-south-1": + return "ami-531a4c3c" + case "ap-southeast-1": + return "ami-68097514" + case "ap-southeast-2": + return "ami-942dd1f6" + case "sa-east-1": + return "ami-84175ae8" + default: + return "" + } +} + func createTestEC2Instance(t *testing.T, session *session.Session, name string) ec2.Instance { svc := ec2.New(session) params := &ec2.RunInstancesInput{ - ImageId: awsgo.String("ami-e7527ed7"), + ImageId: awsgo.String(getLinuxAmiIDForRegion(*session.Config.Region)), InstanceType: awsgo.String("t1.micro"), MinCount: awsgo.Int64(1), MaxCount: awsgo.Int64(1), @@ -43,7 +80,7 @@ func createTestEC2Instance(t *testing.T, session *session.Session, name string) runResult, err := svc.RunInstances(params) if err != nil { - assert.Failf(t, "Could not create test EC2 instance: %s", errors.WithStackTrace(err).Error()) + assert.Fail(t, "Could not create test EC2 instance") } err = svc.WaitUntilInstanceExists(&ec2.DescribeInstancesInput{ From e913e17f4f39436fcc654352878f7b8929823984 Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Tue, 6 Feb 2018 12:23:30 +0100 Subject: [PATCH 24/38] Move unique id function to separate go package file --- aws/asg_test.go | 5 +++-- aws/ebs_test.go | 5 +++-- aws/ec2_test.go | 26 +++----------------------- aws/elb_test.go | 5 +++-- aws/elbv2_test.go | 5 +++-- util/unique_id.go | 25 +++++++++++++++++++++++++ 6 files changed, 40 insertions(+), 31 deletions(-) create mode 100644 util/unique_id.go diff --git a/aws/asg_test.go b/aws/asg_test.go index 4d4c4181..4d9cc16b 100644 --- a/aws/asg_test.go +++ b/aws/asg_test.go @@ -7,6 +7,7 @@ import ( "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/autoscaling" "github.com/aws/aws-sdk-go/service/ec2" + "github.com/gruntwork-io/aws-nuke/util" "github.com/gruntwork-io/gruntwork-cli/errors" "github.com/stretchr/testify/assert" ) @@ -62,7 +63,7 @@ func TestListAutoScalingGroups(t *testing.T) { assert.Fail(t, errors.WithStackTrace(err).Error()) } - groupName := "aws-nuke-test-" + uniqueID() + groupName := "aws-nuke-test-" + util.UniqueID() createTestAutoScalingGroup(t, session, groupName) groupNames, err := getAllAutoScalingGroups(session, region) @@ -86,7 +87,7 @@ func TestNukeAutoScalingGroups(t *testing.T) { assert.Fail(t, errors.WithStackTrace(err).Error()) } - groupName := "aws-nuke-test-" + uniqueID() + groupName := "aws-nuke-test-" + util.UniqueID() createTestAutoScalingGroup(t, session, groupName) _, err = svc.DescribeAutoScalingGroups(&autoscaling.DescribeAutoScalingGroupsInput{ diff --git a/aws/ebs_test.go b/aws/ebs_test.go index 370f179f..abce0106 100644 --- a/aws/ebs_test.go +++ b/aws/ebs_test.go @@ -6,6 +6,7 @@ import ( awsgo "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/ec2" + "github.com/gruntwork-io/aws-nuke/util" "github.com/gruntwork-io/gruntwork-cli/errors" "github.com/stretchr/testify/assert" ) @@ -73,7 +74,7 @@ func TestListEBSVolumes(t *testing.T) { assert.Fail(t, errors.WithStackTrace(err).Error()) } - uniqueTestID := "aws-nuke-test-" + uniqueID() + uniqueTestID := "aws-nuke-test-" + util.UniqueID() volume := createTestEBSVolume(t, session, uniqueTestID) volumeIds, err := getAllEbsVolumes(session, region) @@ -96,7 +97,7 @@ func TestNukeEBSVolumes(t *testing.T) { assert.Fail(t, errors.WithStackTrace(err).Error()) } - uniqueTestID := "aws-nuke-test-" + uniqueID() + uniqueTestID := "aws-nuke-test-" + util.UniqueID() createTestEC2Instance(t, session, uniqueTestID) output, err := ec2.New(session).DescribeVolumes(&ec2.DescribeVolumesInput{}) diff --git a/aws/ec2_test.go b/aws/ec2_test.go index 0361ddf2..ff7b7756 100644 --- a/aws/ec2_test.go +++ b/aws/ec2_test.go @@ -1,36 +1,16 @@ package aws import ( - "bytes" - "math/rand" "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/ec2" + "github.com/gruntwork-io/aws-nuke/util" "github.com/gruntwork-io/gruntwork-cli/errors" "github.com/stretchr/testify/assert" ) -// Returns a unique (ish) id we can attach to resources and tfstate files so they don't conflict with each other -// Uses base 62 to generate a 6 character string that's unlikely to collide with the handful of tests we run in -// parallel. Based on code here: http://stackoverflow.com/a/9543797/483528 -func uniqueID() string { - - const BASE_62_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" - const UNIQUE_ID_LENGTH = 6 // Should be good for 62^6 = 56+ billion combinations - - var out bytes.Buffer - - rand := rand.New(rand.NewSource(time.Now().UnixNano())) - for i := 0; i < UNIQUE_ID_LENGTH; i++ { - out.WriteByte(BASE_62_CHARS[rand.Intn(len(BASE_62_CHARS))]) - } - - return out.String() -} - func getLinuxAmiIDForRegion(region string) string { switch region { case "us-east-1": @@ -147,7 +127,7 @@ func TestListInstances(t *testing.T) { assert.Fail(t, errors.WithStackTrace(err).Error()) } - uniqueTestID := "aws-nuke-test-" + uniqueID() + uniqueTestID := "aws-nuke-test-" + util.UniqueID() instance := createTestEC2Instance(t, session, uniqueTestID) instanceIds, err := getAllEc2Instances(session, region) @@ -170,7 +150,7 @@ func TestNukeInstances(t *testing.T) { assert.Fail(t, errors.WithStackTrace(err).Error()) } - uniqueTestID := "aws-nuke-test-" + uniqueID() + uniqueTestID := "aws-nuke-test-" + util.UniqueID() createTestEC2Instance(t, session, uniqueTestID) output, err := ec2.New(session).DescribeInstances(&ec2.DescribeInstancesInput{}) diff --git a/aws/elb_test.go b/aws/elb_test.go index 441f420e..37479a85 100644 --- a/aws/elb_test.go +++ b/aws/elb_test.go @@ -6,6 +6,7 @@ import ( awsgo "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/elb" + "github.com/gruntwork-io/aws-nuke/util" "github.com/gruntwork-io/gruntwork-cli/errors" "github.com/stretchr/testify/assert" ) @@ -45,7 +46,7 @@ func TestListELBs(t *testing.T) { assert.Fail(t, errors.WithStackTrace(err).Error()) } - elbName := "aws-nuke-test-" + uniqueID() + elbName := "aws-nuke-test-" + util.UniqueID() createTestELB(t, session, elbName) elbNames, err := getAllElbInstances(session, region) @@ -69,7 +70,7 @@ func TestNukeELBs(t *testing.T) { assert.Fail(t, errors.WithStackTrace(err).Error()) } - elbName := "aws-nuke-test-" + uniqueID() + elbName := "aws-nuke-test-" + util.UniqueID() createTestELB(t, session, elbName) _, err = svc.DescribeLoadBalancers(&elb.DescribeLoadBalancersInput{ diff --git a/aws/elbv2_test.go b/aws/elbv2_test.go index f1387915..6a694174 100644 --- a/aws/elbv2_test.go +++ b/aws/elbv2_test.go @@ -7,6 +7,7 @@ import ( "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/elbv2" + "github.com/gruntwork-io/aws-nuke/util" "github.com/gruntwork-io/gruntwork-cli/errors" "github.com/stretchr/testify/assert" ) @@ -69,7 +70,7 @@ func TestListELBv2(t *testing.T) { assert.Fail(t, errors.WithStackTrace(err).Error()) } - elbName := "aws-nuke-test-" + uniqueID() + elbName := "aws-nuke-test-" + util.UniqueID() balancer := createTestELBv2(t, session, elbName) arns, err := getAllElbv2Instances(session, region) @@ -93,7 +94,7 @@ func TestNukeELBv2(t *testing.T) { assert.Fail(t, errors.WithStackTrace(err).Error()) } - elbName := "aws-nuke-test-" + uniqueID() + elbName := "aws-nuke-test-" + util.UniqueID() balancer := createTestELBv2(t, session, elbName) _, err = svc.DescribeLoadBalancers(&elbv2.DescribeLoadBalancersInput{ diff --git a/util/unique_id.go b/util/unique_id.go new file mode 100644 index 00000000..bb79863c --- /dev/null +++ b/util/unique_id.go @@ -0,0 +1,25 @@ +package util + +import ( + "bytes" + "math/rand" + "time" +) + +// Returns a unique (ish) id we can attach to resources and tfstate files so they don't conflict with each other +// Uses base 62 to generate a 6 character string that's unlikely to collide with the handful of tests we run in +// parallel. Based on code here: http://stackoverflow.com/a/9543797/483528 +func UniqueID() string { + + const BASE_62_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + const UNIQUE_ID_LENGTH = 6 // Should be good for 62^6 = 56+ billion combinations + + var out bytes.Buffer + + rand := rand.New(rand.NewSource(time.Now().UnixNano())) + for i := 0; i < UNIQUE_ID_LENGTH; i++ { + out.WriteByte(BASE_62_CHARS[rand.Intn(len(BASE_62_CHARS))]) + } + + return out.String() +} From 90dd2c7d9cb4dad0dbda1d0b54e10d9b59652a3c Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Tue, 6 Feb 2018 13:26:23 +0100 Subject: [PATCH 25/38] clean up resources created in list tests --- aws/asg_test.go | 3 +++ aws/ebs_test.go | 3 +++ aws/ec2_test.go | 3 +++ aws/elb_test.go | 3 +++ aws/elbv2_test.go | 3 +++ 5 files changed, 15 insertions(+) diff --git a/aws/asg_test.go b/aws/asg_test.go index 4d9cc16b..2e6dd9cc 100644 --- a/aws/asg_test.go +++ b/aws/asg_test.go @@ -72,6 +72,9 @@ func TestListAutoScalingGroups(t *testing.T) { } assert.Contains(t, awsgo.StringValueSlice(groupNames), groupName) + + // clean up after this test + defer nukeAllAutoScalingGroups(session, []*string{&groupName}) } func TestNukeAutoScalingGroups(t *testing.T) { diff --git a/aws/ebs_test.go b/aws/ebs_test.go index abce0106..cca5c536 100644 --- a/aws/ebs_test.go +++ b/aws/ebs_test.go @@ -83,6 +83,9 @@ func TestListEBSVolumes(t *testing.T) { } assert.Contains(t, awsgo.StringValueSlice(volumeIds), awsgo.StringValue(volume.VolumeId)) + + // clean up after this test + defer nukeAllEbsVolumes(session, []*string{volume.VolumeId}) } func TestNukeEBSVolumes(t *testing.T) { diff --git a/aws/ec2_test.go b/aws/ec2_test.go index ff7b7756..1e58c0cc 100644 --- a/aws/ec2_test.go +++ b/aws/ec2_test.go @@ -136,6 +136,9 @@ func TestListInstances(t *testing.T) { } assert.Contains(t, instanceIds, instance.InstanceId) + + // clean up after this test + defer nukeAllEc2Instances(session, []*string{instance.InstanceId}) } func TestNukeInstances(t *testing.T) { diff --git a/aws/elb_test.go b/aws/elb_test.go index 37479a85..89c7c642 100644 --- a/aws/elb_test.go +++ b/aws/elb_test.go @@ -55,6 +55,9 @@ func TestListELBs(t *testing.T) { } assert.Contains(t, awsgo.StringValueSlice(elbNames), elbName) + + // clean up after this test + defer nukeAllElbInstances(session, []*string{&elbName}) } func TestNukeELBs(t *testing.T) { diff --git a/aws/elbv2_test.go b/aws/elbv2_test.go index 6a694174..8c8700e8 100644 --- a/aws/elbv2_test.go +++ b/aws/elbv2_test.go @@ -79,6 +79,9 @@ func TestListELBv2(t *testing.T) { } assert.Contains(t, awsgo.StringValueSlice(arns), awsgo.StringValue(balancer.LoadBalancerArn)) + + // clean up after this test + defer nukeAllElbv2Instances(session, []*string{balancer.LoadBalancerArn}) } func TestNukeELBv2(t *testing.T) { From 2ceecca473dc90012f0b8669f8df564e3d8111ba Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Tue, 6 Feb 2018 14:54:48 +0100 Subject: [PATCH 26/38] add ability to exclude resources in certain regions from being deleted --- README.md | 8 ++++++++ aws/aws.go | 7 ++++++- commands/cli.go | 8 +++++++- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9b879500..20ea3acd 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,14 @@ The currently supported functionality includes: Simply running `aws-nuke` will start the process of cleaning up your AWS account. You'll be shown a list of resources that'll be deleted as well as a prompt to confirm before any deletion actually takes place. +### Excluding Regions + +You can use the `--exclude` or `-e` flag to exclude resources in certain regions from being deleted. For example the following command does not nuke resources in `ap-south-1` and `ap-south-2` regions: + +```shell +aws-nuke --exclude ap-south-1 --exclude ap-south-2 +``` + Happy Nuking!!! ## Credentials diff --git a/aws/aws.go b/aws/aws.go index a2be3973..8cf4795e 100644 --- a/aws/aws.go +++ b/aws/aws.go @@ -41,12 +41,17 @@ func getRandomRegion() string { } // GetAllResources - Lists all aws resources -func GetAllResources() (*AwsAccountResources, error) { +func GetAllResources(excludedRegions []string) (*AwsAccountResources, error) { account := AwsAccountResources{ Resources: make(map[string]AwsRegionResource), } for _, region := range getAllRegions() { + // Ignore all cli excluded regions + if collections.ListContainsElement(excludedRegions, region) { + continue + } + session, err := session.NewSession(&awsgo.Config{ Region: awsgo.String(region)}, ) diff --git a/commands/cli.go b/commands/cli.go index bc12e787..fe79608c 100644 --- a/commands/cli.go +++ b/commands/cli.go @@ -20,6 +20,12 @@ func CreateCli(version string) *cli.App { app.Author = "Gruntwork " app.Version = version app.Usage = "A CLI tool to cleanup AWS resources (ASG, ELB, ELBv2, EBS, EC2). THIS TOOL WILL COMPLETELY REMOVE ALL RESOURCES AND ITS EFFECTS ARE IRREVERSIBLE!!!" + app.Flags = []cli.Flag{ + cli.StringSliceFlag{ + Name: "exclude, e", + Usage: "regions to exclude", + }, + } app.Action = errors.WithPanicHandling(awsNuke) return app @@ -29,7 +35,7 @@ func CreateCli(version string) *cli.App { func awsNuke(c *cli.Context) error { logging.Logger.Infoln("Retrieving all active AWS resources") - account, err := aws.GetAllResources() + account, err := aws.GetAllResources(c.StringSlice("exclude")) if err != nil { return errors.WithStackTrace(err) From 5d1af2219060754e47991516e4e4d230fa62c053 Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Tue, 6 Feb 2018 15:45:50 +0100 Subject: [PATCH 27/38] change name of exclude region flag --- README.md | 4 ++-- commands/cli.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 20ea3acd..cce00436 100644 --- a/README.md +++ b/README.md @@ -24,10 +24,10 @@ Simply running `aws-nuke` will start the process of cleaning up your AWS account ### Excluding Regions -You can use the `--exclude` or `-e` flag to exclude resources in certain regions from being deleted. For example the following command does not nuke resources in `ap-south-1` and `ap-south-2` regions: +You can use the `--exclude-region` flag to exclude resources in certain regions from being deleted. For example the following command does not nuke resources in `ap-south-1` and `ap-south-2` regions: ```shell -aws-nuke --exclude ap-south-1 --exclude ap-south-2 +aws-nuke --exclude-region ap-south-1 --exclude-region ap-south-2 ``` Happy Nuking!!! diff --git a/commands/cli.go b/commands/cli.go index fe79608c..860d8f7b 100644 --- a/commands/cli.go +++ b/commands/cli.go @@ -22,7 +22,7 @@ func CreateCli(version string) *cli.App { app.Usage = "A CLI tool to cleanup AWS resources (ASG, ELB, ELBv2, EBS, EC2). THIS TOOL WILL COMPLETELY REMOVE ALL RESOURCES AND ITS EFFECTS ARE IRREVERSIBLE!!!" app.Flags = []cli.Flag{ cli.StringSliceFlag{ - Name: "exclude, e", + Name: "exclude-region", Usage: "regions to exclude", }, } From a3c38be84dd87587cc6f3519ffde842492db3705 Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Tue, 6 Feb 2018 15:45:58 +0100 Subject: [PATCH 28/38] fix typo --- aws/ebs_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/ebs_test.go b/aws/ebs_test.go index cca5c536..8a8c1a29 100644 --- a/aws/ebs_test.go +++ b/aws/ebs_test.go @@ -51,7 +51,7 @@ func createTestEBSVolume(t *testing.T, session *session.Session, name string) ec func findEBSVolumesByNameTag(output *ec2.DescribeVolumesOutput, name string) []*string { var volumeIds []*string for _, volume := range output.Volumes { - // Retrive only IDs of instances with the unique test tag + // Retrieve only IDs of instances with the unique test tag for _, tag := range volume.Tags { if awsgo.StringValue(tag.Key) == "Name" && awsgo.StringValue(tag.Value) == name { volumeIds = append(volumeIds, volume.VolumeId) From 88b5c0fd25563450f41a52a40bb75b01254279f7 Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Tue, 6 Feb 2018 20:02:39 +0100 Subject: [PATCH 29/38] validate excluded regions --- aws/aws.go | 14 +++++++------- commands/cli.go | 18 +++++++++++++++--- commands/errors.go | 7 +++++++ 3 files changed, 29 insertions(+), 10 deletions(-) create mode 100644 commands/errors.go diff --git a/aws/aws.go b/aws/aws.go index 8cf4795e..0521ac76 100644 --- a/aws/aws.go +++ b/aws/aws.go @@ -11,8 +11,8 @@ import ( "github.com/gruntwork-io/gruntwork-cli/errors" ) -// Returns a list of all AWS regions -func getAllRegions() []string { +// GetAllRegions - Returns a list of all AWS regions +func GetAllRegions() []string { // chinese and government regions are not accessible with regular accounts reservedRegions := []string{ "cn-north-1", "cn-northwest-1", "us-gov-west-1", @@ -34,19 +34,19 @@ func getAllRegions() []string { } func getRandomRegion() string { - allRegions := getAllRegions() + allRegions := GetAllRegions() rand.Seed(time.Now().UnixNano()) randIndex := rand.Intn(len(allRegions)) return allRegions[randIndex] } // GetAllResources - Lists all aws resources -func GetAllResources(excludedRegions []string) (*AwsAccountResources, error) { +func GetAllResources(regions []string, excludedRegions []string) (*AwsAccountResources, error) { account := AwsAccountResources{ Resources: make(map[string]AwsRegionResource), } - for _, region := range getAllRegions() { + for _, region := range regions { // Ignore all cli excluded regions if collections.ListContainsElement(excludedRegions, region) { continue @@ -134,8 +134,8 @@ func GetAllResources(excludedRegions []string) (*AwsAccountResources, error) { } // NukeAllResources - Nukes all aws resources -func NukeAllResources(account *AwsAccountResources) error { - for _, region := range getAllRegions() { +func NukeAllResources(account *AwsAccountResources, regions []string) error { + for _, region := range regions { session, err := session.NewSession(&awsgo.Config{ Region: awsgo.String(region)}, ) diff --git a/commands/cli.go b/commands/cli.go index 860d8f7b..fa6d714d 100644 --- a/commands/cli.go +++ b/commands/cli.go @@ -1,8 +1,11 @@ package commands import ( + "fmt" "strings" + "github.com/gruntwork-io/gruntwork-cli/collections" + "github.com/fatih/color" "github.com/gruntwork-io/aws-nuke/aws" "github.com/gruntwork-io/aws-nuke/logging" @@ -33,9 +36,18 @@ func CreateCli(version string) *cli.App { // Nuke it all!!! func awsNuke(c *cli.Context) error { - logging.Logger.Infoln("Retrieving all active AWS resources") + regions := aws.GetAllRegions() + excludedRegions := c.StringSlice("exclude-region") + + for _, excludedRegion := range excludedRegions { + if !collections.ListContainsElement(regions, excludedRegion) { + fmt.Println(excludedRegion + "is not a valid AWS Region") + return InvalidFlagError{} + } + } - account, err := aws.GetAllResources(c.StringSlice("exclude")) + logging.Logger.Infoln("Retrieving all active AWS resources") + account, err := aws.GetAllResources(regions, excludedRegions) if err != nil { return errors.WithStackTrace(err) @@ -66,7 +78,7 @@ func awsNuke(c *cli.Context) error { } if strings.ToLower(input) == "nuke" { - aws.NukeAllResources(account) + aws.NukeAllResources(account, regions) } return nil diff --git a/commands/errors.go b/commands/errors.go new file mode 100644 index 00000000..68f65194 --- /dev/null +++ b/commands/errors.go @@ -0,0 +1,7 @@ +package commands + +type InvalidFlagError struct{} + +func (e InvalidFlagError) Error() string { + return "Invalid flag" +} From 6187bab9767b617a3e4e040275b8a3114c4642dc Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Tue, 6 Feb 2018 20:05:00 +0100 Subject: [PATCH 30/38] createTestEC2Instance should wait for created instance to be in the running state --- aws/asg_test.go | 10 ---------- aws/ec2_test.go | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/aws/asg_test.go b/aws/asg_test.go index 2e6dd9cc..e7d4051e 100644 --- a/aws/asg_test.go +++ b/aws/asg_test.go @@ -16,16 +16,6 @@ func createTestAutoScalingGroup(t *testing.T, session *session.Session, name str svc := autoscaling.New(session) instance := createTestEC2Instance(t, session, name) - // EC2 Instance must be in a running state before ASG can be created - err := ec2.New(session).WaitUntilInstanceRunning(&ec2.DescribeInstancesInput{ - Filters: []*ec2.Filter{ - &ec2.Filter{ - Name: awsgo.String("instance-id"), - Values: []*string{instance.InstanceId}, - }, - }, - }) - if err != nil { assert.Fail(t, errors.WithStackTrace(err).Error()) } diff --git a/aws/ec2_test.go b/aws/ec2_test.go index 1e58c0cc..797073ff 100644 --- a/aws/ec2_test.go +++ b/aws/ec2_test.go @@ -91,6 +91,20 @@ func createTestEC2Instance(t *testing.T, session *session.Session, name string) assert.Failf(t, "Could not tag EC2 instance: %s", errors.WithStackTrace(err).Error()) } + // EC2 Instance must be in a running before this function returns + err = ec2.New(session).WaitUntilInstanceRunning(&ec2.DescribeInstancesInput{ + Filters: []*ec2.Filter{ + &ec2.Filter{ + Name: awsgo.String("instance-id"), + Values: []*string{runResult.Instances[0].InstanceId}, + }, + }, + }) + + if err != nil { + assert.Fail(t, errors.WithStackTrace(err).Error()) + } + return *runResult.Instances[0] } From 8bf36f930bbe22e96d705fb1390afe2d58f58635 Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Tue, 6 Feb 2018 20:45:57 +0100 Subject: [PATCH 31/38] improve logging and overall cli UX --- aws/asg.go | 2 ++ aws/asg_test.go | 7 +------ aws/aws.go | 5 +++++ aws/ebs.go | 2 ++ aws/ec2.go | 4 ++++ aws/elb.go | 2 ++ aws/elbv2.go | 2 ++ commands/cli.go | 7 +++++-- 8 files changed, 23 insertions(+), 8 deletions(-) diff --git a/aws/asg.go b/aws/asg.go index ad7ccacb..a7d6298b 100644 --- a/aws/asg.go +++ b/aws/asg.go @@ -46,6 +46,8 @@ func nukeAllAutoScalingGroups(session *session.Session, groupNames []*string) er logging.Logger.Errorf("[Failed] %s", err) return errors.WithStackTrace(err) } + + logging.Logger.Infof("Deleted Auto Scaling Group: %s", *groupName) } err := svc.WaitUntilGroupNotExists(&autoscaling.DescribeAutoScalingGroupsInput{ diff --git a/aws/asg_test.go b/aws/asg_test.go index e7d4051e..fee1bbe7 100644 --- a/aws/asg_test.go +++ b/aws/asg_test.go @@ -6,7 +6,6 @@ import ( 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/aws/aws-sdk-go/service/ec2" "github.com/gruntwork-io/aws-nuke/util" "github.com/gruntwork-io/gruntwork-cli/errors" "github.com/stretchr/testify/assert" @@ -16,10 +15,6 @@ func createTestAutoScalingGroup(t *testing.T, session *session.Session, name str svc := autoscaling.New(session) instance := createTestEC2Instance(t, session, name) - if err != nil { - assert.Fail(t, errors.WithStackTrace(err).Error()) - } - param := &autoscaling.CreateAutoScalingGroupInput{ AutoScalingGroupName: &name, InstanceId: instance.InstanceId, @@ -27,7 +22,7 @@ func createTestAutoScalingGroup(t *testing.T, session *session.Session, name str MaxSize: awsgo.Int64(2), } - _, err = svc.CreateAutoScalingGroup(param) + _, err := svc.CreateAutoScalingGroup(param) if err != nil { assert.Failf(t, "Could not create test ASG: %s", errors.WithStackTrace(err).Error()) } diff --git a/aws/aws.go b/aws/aws.go index 0521ac76..5a45a690 100644 --- a/aws/aws.go +++ b/aws/aws.go @@ -7,6 +7,7 @@ import ( awsgo "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/endpoints" "github.com/aws/aws-sdk-go/aws/session" + "github.com/gruntwork-io/aws-nuke/logging" "github.com/gruntwork-io/gruntwork-cli/collections" "github.com/gruntwork-io/gruntwork-cli/errors" ) @@ -49,6 +50,7 @@ func GetAllResources(regions []string, excludedRegions []string) (*AwsAccountRes for _, region := range regions { // Ignore all cli excluded regions if collections.ListContainsElement(excludedRegions, region) { + logging.Logger.Infoln("Skipping region: " + region) continue } @@ -62,6 +64,9 @@ func GetAllResources(regions []string, excludedRegions []string) (*AwsAccountRes resourcesInRegion := AwsRegionResource{} + // The order in which resources are nuked is important + // because of dependencies between resources + // ASG Names groupNames, err := getAllAutoScalingGroups(session, region) if err != nil { diff --git a/aws/ebs.go b/aws/ebs.go index 71548f41..4f842d67 100644 --- a/aws/ebs.go +++ b/aws/ebs.go @@ -51,6 +51,8 @@ func nukeAllEbsVolumes(session *session.Session, volumeIds []*string) error { } logging.Logger.Infof("EBS volume %s has already been deleted", *volumeID) + } else { + logging.Logger.Infof("Deleted EBS Volume: %s", *volumeID) } } diff --git a/aws/ec2.go b/aws/ec2.go index a1db65e1..786a7415 100644 --- a/aws/ec2.go +++ b/aws/ec2.go @@ -85,6 +85,10 @@ func nukeAllEc2Instances(session *session.Session, instanceIds []*string) error return errors.WithStackTrace(err) } + for _, instanceID := range instanceIds { + logging.Logger.Infof("Terminated EC2 Instance: %s", *instanceID) + } + err = svc.WaitUntilInstanceTerminated(&ec2.DescribeInstancesInput{ Filters: []*ec2.Filter{ &ec2.Filter{ diff --git a/aws/elb.go b/aws/elb.go index 9414860c..08bb4484 100644 --- a/aws/elb.go +++ b/aws/elb.go @@ -44,6 +44,8 @@ func nukeAllElbInstances(session *session.Session, names []*string) error { logging.Logger.Errorf("[Failed] %s", err) return errors.WithStackTrace(err) } + + logging.Logger.Infof("Deleted ELB: %s", *name) } logging.Logger.Infof("[OK] %d Elastic Load Balancer(s) deleted in %s", len(names), *session.Config.Region) diff --git a/aws/elbv2.go b/aws/elbv2.go index 2159897c..78fd9cb0 100644 --- a/aws/elbv2.go +++ b/aws/elbv2.go @@ -44,6 +44,8 @@ func nukeAllElbv2Instances(session *session.Session, arns []*string) error { logging.Logger.Errorf("[Failed] %s", err) return errors.WithStackTrace(err) } + + logging.Logger.Infof("Deleted ELBv2: %s", *arn) } err := svc.WaitUntilLoadBalancersDeleted(&elbv2.DescribeLoadBalancersInput{ diff --git a/commands/cli.go b/commands/cli.go index fa6d714d..9e2d0b04 100644 --- a/commands/cli.go +++ b/commands/cli.go @@ -46,7 +46,7 @@ func awsNuke(c *cli.Context) error { } } - logging.Logger.Infoln("Retrieving all active AWS resources") + fmt.Println("Retrieving all active AWS resources") account, err := aws.GetAllResources(regions, excludedRegions) if err != nil { @@ -58,10 +58,13 @@ func awsNuke(c *cli.Context) error { return nil } + fmt.Println("The following AWS resources are going to be nuked: ") + fmt.Println() + for region, resourcesInRegion := range account.Resources { for _, resources := range resourcesInRegion.Resources { for _, identifier := range resources.ResourceIdentifiers() { - logging.Logger.Infof("%s-%s-%s", resources.ResourceName(), identifier, region) + fmt.Printf("* %s-%s-%s\n", resources.ResourceName(), identifier, region) } } } From f18e711d5016b56e0ab6b1da1d27980d36469ce6 Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Wed, 7 Feb 2018 12:19:44 +0100 Subject: [PATCH 32/38] wait until elb is deleted before returning from nuke function --- aws/elb.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/aws/elb.go b/aws/elb.go index 08bb4484..c491edf3 100644 --- a/aws/elb.go +++ b/aws/elb.go @@ -1,12 +1,28 @@ package aws import ( + "time" + "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/elb" "github.com/gruntwork-io/aws-nuke/logging" "github.com/gruntwork-io/gruntwork-cli/errors" ) +func waitUntilElbDeleted(svc *elb.ELB, input *elb.DescribeLoadBalancersInput) error { + for i := 0; i < 30; i++ { + _, err := svc.DescribeLoadBalancers(input) + if err != nil { + // an error is returned when ELB no longer exists + return nil + } + + time.Sleep(1 * time.Second) + } + + panic("ELBs failed to delete") +} + // Returns a formatted string of ELB names func getAllElbInstances(session *session.Session, region string) ([]*string, error) { svc := elb.New(session) @@ -48,6 +64,14 @@ func nukeAllElbInstances(session *session.Session, names []*string) error { logging.Logger.Infof("Deleted ELB: %s", *name) } + err := waitUntilElbDeleted(svc, &elb.DescribeLoadBalancersInput{ + LoadBalancerNames: names, + }) + + if err != nil { + return errors.WithStackTrace(err) + } + logging.Logger.Infof("[OK] %d Elastic Load Balancer(s) deleted in %s", len(names), *session.Config.Region) return nil } From d6ce82ccada6580f776f774de8c2e5a0c431b4aa Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Wed, 7 Feb 2018 13:17:04 +0100 Subject: [PATCH 33/38] use dynamically selected images to create ec2 instance --- aws/ec2_test.go | 63 ++++++++++++++++++------------------------------- 1 file changed, 23 insertions(+), 40 deletions(-) diff --git a/aws/ec2_test.go b/aws/ec2_test.go index 797073ff..34c4cdb6 100644 --- a/aws/ec2_test.go +++ b/aws/ec2_test.go @@ -11,55 +11,38 @@ import ( "github.com/stretchr/testify/assert" ) -func getLinuxAmiIDForRegion(region string) string { - switch region { - case "us-east-1": - return "ami-97785bed" - case "us-east-2": - return "ami-f63b1193" - case "us-west-1": - return "ami-824c4ee2" - case "us-west-2": - return "ami-f2d3638a" - case "ca-central-1": - return "ami-a954d1cd" - case "eu-west-1": - return "ami-d834aba1" - case "eu-west-2": - return "ami-403e2524" - case "eu-west-3": - return "ami-8ee056f3" - case "eu-central-1": - return "ami-5652ce39" - case "ap-northeast-1": - return "ami-ceafcba8" - case "ap-northeast-2": - return "ami-863090e8" - case "ap-south-1": - return "ami-531a4c3c" - case "ap-southeast-1": - return "ami-68097514" - case "ap-southeast-2": - return "ami-942dd1f6" - case "sa-east-1": - return "ami-84175ae8" - default: - return "" - } -} - func createTestEC2Instance(t *testing.T, session *session.Session, name string) ec2.Instance { svc := ec2.New(session) + imagesResult, err := svc.DescribeImages(&ec2.DescribeImagesInput{ + Owners: []*string{awsgo.String("self"), awsgo.String("amazon")}, + Filters: []*ec2.Filter{ + &ec2.Filter{ + Name: awsgo.String("root-device-type"), + Values: []*string{awsgo.String("ebs")}, + }, + &ec2.Filter{ + Name: awsgo.String("virtualization-type"), + Values: []*string{awsgo.String("hvm")}, + }, + }, + }) + + if err != nil { + assert.Fail(t, errors.WithStackTrace(err).Error()) + } + + imageID := *imagesResult.Images[0].ImageId + params := &ec2.RunInstancesInput{ - ImageId: awsgo.String(getLinuxAmiIDForRegion(*session.Config.Region)), + ImageId: awsgo.String(imageID), InstanceType: awsgo.String("t1.micro"), MinCount: awsgo.Int64(1), MaxCount: awsgo.Int64(1), } runResult, err := svc.RunInstances(params) - if err != nil { + if err != nil || len(runResult.Instances) == 0 { assert.Fail(t, "Could not create test EC2 instance") } @@ -92,7 +75,7 @@ func createTestEC2Instance(t *testing.T, session *session.Session, name string) } // EC2 Instance must be in a running before this function returns - err = ec2.New(session).WaitUntilInstanceRunning(&ec2.DescribeInstancesInput{ + err = svc.WaitUntilInstanceRunning(&ec2.DescribeInstancesInput{ Filters: []*ec2.Filter{ &ec2.Filter{ Name: awsgo.String("instance-id"), From 146c760eb9de310e5b65b6fd7030f288e4a410fd Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Wed, 7 Feb 2018 15:29:03 +0100 Subject: [PATCH 34/38] re-order cleanup defer calls --- aws/asg_test.go | 5 ++--- aws/ebs_test.go | 5 ++--- aws/ec2_test.go | 6 +++--- aws/elb_test.go | 5 ++--- aws/elbv2_test.go | 5 ++--- 5 files changed, 11 insertions(+), 15 deletions(-) diff --git a/aws/asg_test.go b/aws/asg_test.go index fee1bbe7..a56781f6 100644 --- a/aws/asg_test.go +++ b/aws/asg_test.go @@ -50,6 +50,8 @@ func TestListAutoScalingGroups(t *testing.T) { groupName := "aws-nuke-test-" + util.UniqueID() createTestAutoScalingGroup(t, session, groupName) + // clean up after this test + defer nukeAllAutoScalingGroups(session, []*string{&groupName}) groupNames, err := getAllAutoScalingGroups(session, region) if err != nil { @@ -57,9 +59,6 @@ func TestListAutoScalingGroups(t *testing.T) { } assert.Contains(t, awsgo.StringValueSlice(groupNames), groupName) - - // clean up after this test - defer nukeAllAutoScalingGroups(session, []*string{&groupName}) } func TestNukeAutoScalingGroups(t *testing.T) { diff --git a/aws/ebs_test.go b/aws/ebs_test.go index 8a8c1a29..6e6a10ae 100644 --- a/aws/ebs_test.go +++ b/aws/ebs_test.go @@ -76,6 +76,8 @@ func TestListEBSVolumes(t *testing.T) { uniqueTestID := "aws-nuke-test-" + util.UniqueID() volume := createTestEBSVolume(t, session, uniqueTestID) + // clean up after this test + defer nukeAllEbsVolumes(session, []*string{volume.VolumeId}) volumeIds, err := getAllEbsVolumes(session, region) if err != nil { @@ -83,9 +85,6 @@ func TestListEBSVolumes(t *testing.T) { } assert.Contains(t, awsgo.StringValueSlice(volumeIds), awsgo.StringValue(volume.VolumeId)) - - // clean up after this test - defer nukeAllEbsVolumes(session, []*string{volume.VolumeId}) } func TestNukeEBSVolumes(t *testing.T) { diff --git a/aws/ec2_test.go b/aws/ec2_test.go index 34c4cdb6..2d116f5a 100644 --- a/aws/ec2_test.go +++ b/aws/ec2_test.go @@ -126,6 +126,9 @@ func TestListInstances(t *testing.T) { uniqueTestID := "aws-nuke-test-" + util.UniqueID() instance := createTestEC2Instance(t, session, uniqueTestID) + // clean up after this test + defer nukeAllEc2Instances(session, []*string{instance.InstanceId}) + instanceIds, err := getAllEc2Instances(session, region) if err != nil { @@ -133,9 +136,6 @@ func TestListInstances(t *testing.T) { } assert.Contains(t, instanceIds, instance.InstanceId) - - // clean up after this test - defer nukeAllEc2Instances(session, []*string{instance.InstanceId}) } func TestNukeInstances(t *testing.T) { diff --git a/aws/elb_test.go b/aws/elb_test.go index 89c7c642..40e3af63 100644 --- a/aws/elb_test.go +++ b/aws/elb_test.go @@ -48,6 +48,8 @@ func TestListELBs(t *testing.T) { elbName := "aws-nuke-test-" + util.UniqueID() createTestELB(t, session, elbName) + // clean up after this test + defer nukeAllElbInstances(session, []*string{&elbName}) elbNames, err := getAllElbInstances(session, region) if err != nil { @@ -55,9 +57,6 @@ func TestListELBs(t *testing.T) { } assert.Contains(t, awsgo.StringValueSlice(elbNames), elbName) - - // clean up after this test - defer nukeAllElbInstances(session, []*string{&elbName}) } func TestNukeELBs(t *testing.T) { diff --git a/aws/elbv2_test.go b/aws/elbv2_test.go index 8c8700e8..a1055f3a 100644 --- a/aws/elbv2_test.go +++ b/aws/elbv2_test.go @@ -72,6 +72,8 @@ func TestListELBv2(t *testing.T) { elbName := "aws-nuke-test-" + util.UniqueID() balancer := createTestELBv2(t, session, elbName) + // clean up after this test + defer nukeAllElbv2Instances(session, []*string{balancer.LoadBalancerArn}) arns, err := getAllElbv2Instances(session, region) if err != nil { @@ -79,9 +81,6 @@ func TestListELBv2(t *testing.T) { } assert.Contains(t, awsgo.StringValueSlice(arns), awsgo.StringValue(balancer.LoadBalancerArn)) - - // clean up after this test - defer nukeAllElbv2Instances(session, []*string{balancer.LoadBalancerArn}) } func TestNukeELBv2(t *testing.T) { From e368ad4090ecc372ff2c58bc62bcf8016fe76596 Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Wed, 7 Feb 2018 18:44:07 +0100 Subject: [PATCH 35/38] use the logger package to print log all info --- commands/cli.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/commands/cli.go b/commands/cli.go index 9e2d0b04..ae4f4778 100644 --- a/commands/cli.go +++ b/commands/cli.go @@ -1,7 +1,6 @@ package commands import ( - "fmt" "strings" "github.com/gruntwork-io/gruntwork-cli/collections" @@ -41,12 +40,12 @@ func awsNuke(c *cli.Context) error { for _, excludedRegion := range excludedRegions { if !collections.ListContainsElement(regions, excludedRegion) { - fmt.Println(excludedRegion + "is not a valid AWS Region") + logging.Logger.Infoln(excludedRegion + "is not a valid AWS Region") return InvalidFlagError{} } } - fmt.Println("Retrieving all active AWS resources") + logging.Logger.Infoln("Retrieving all active AWS resources") account, err := aws.GetAllResources(regions, excludedRegions) if err != nil { @@ -58,13 +57,12 @@ func awsNuke(c *cli.Context) error { return nil } - fmt.Println("The following AWS resources are going to be nuked: ") - fmt.Println() + logging.Logger.Infoln("The following AWS resources are going to be nuked: ") for region, resourcesInRegion := range account.Resources { for _, resources := range resourcesInRegion.Resources { for _, identifier := range resources.ResourceIdentifiers() { - fmt.Printf("* %s-%s-%s\n", resources.ResourceName(), identifier, region) + logging.Logger.Infof("* %s-%s-%s\n", resources.ResourceName(), identifier, region) } } } From b010c07914b3752e6e56b86d05d00c68fdac3da7 Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Wed, 7 Feb 2018 18:49:20 +0100 Subject: [PATCH 36/38] update InvalidFlagError to contain more info --- commands/cli.go | 6 ++++-- commands/errors.go | 11 +++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/commands/cli.go b/commands/cli.go index ae4f4778..1f26b03b 100644 --- a/commands/cli.go +++ b/commands/cli.go @@ -40,8 +40,10 @@ func awsNuke(c *cli.Context) error { for _, excludedRegion := range excludedRegions { if !collections.ListContainsElement(regions, excludedRegion) { - logging.Logger.Infoln(excludedRegion + "is not a valid AWS Region") - return InvalidFlagError{} + return InvalidFlagError{ + Name: "exclude-regions", + Value: excludedRegion, + } } } diff --git a/commands/errors.go b/commands/errors.go index 68f65194..2eadde52 100644 --- a/commands/errors.go +++ b/commands/errors.go @@ -1,7 +1,14 @@ package commands -type InvalidFlagError struct{} +import ( + "fmt" +) + +type InvalidFlagError struct { + Name string + Value string +} func (e InvalidFlagError) Error() string { - return "Invalid flag" + return fmt.Sprintf("Invalid value %s for flag %s", e.Value, e.Name) } From 91697913dc2a9cfcde20615c5fd65a40dd2c3241 Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Wed, 7 Feb 2018 19:17:54 +0100 Subject: [PATCH 37/38] distinguish between ec2 creation errors --- aws/ec2_test.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/aws/ec2_test.go b/aws/ec2_test.go index 2d116f5a..aae48ccf 100644 --- a/aws/ec2_test.go +++ b/aws/ec2_test.go @@ -42,7 +42,11 @@ func createTestEC2Instance(t *testing.T, session *session.Session, name string) } runResult, err := svc.RunInstances(params) - if err != nil || len(runResult.Instances) == 0 { + if err != nil { + assert.Fail(t, errors.WithStackTrace(err).Error()) + } + + if len(runResult.Instances) == 0 { assert.Fail(t, "Could not create test EC2 instance") } From fa51aa53d4171f300610b1111401d243c7714da7 Mon Sep 17 00:00:00 2001 From: Oluwatoni Solarin-Sodara Date: Wed, 7 Feb 2018 19:42:26 +0100 Subject: [PATCH 38/38] update waitUntilElbDeleted function to only return success on LoadBalancerNotFound error --- aws/elb.go | 11 ++++++++--- aws/elb_types.go | 6 ++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/aws/elb.go b/aws/elb.go index c491edf3..e0db7950 100644 --- a/aws/elb.go +++ b/aws/elb.go @@ -3,6 +3,7 @@ package aws import ( "time" + "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/elb" "github.com/gruntwork-io/aws-nuke/logging" @@ -13,14 +14,18 @@ func waitUntilElbDeleted(svc *elb.ELB, input *elb.DescribeLoadBalancersInput) er for i := 0; i < 30; i++ { _, err := svc.DescribeLoadBalancers(input) if err != nil { - // an error is returned when ELB no longer exists - return nil + if awsErr, isAwsErr := err.(awserr.Error); isAwsErr && awsErr.Code() == "LoadBalancerNotFound" { + return nil + } + + return err } time.Sleep(1 * time.Second) + logging.Logger.Debug("Waiting for ELB to be deleted") } - panic("ELBs failed to delete") + return ElbDeleteError{} } // Returns a formatted string of ELB names diff --git a/aws/elb_types.go b/aws/elb_types.go index 00a98082..b1175937 100644 --- a/aws/elb_types.go +++ b/aws/elb_types.go @@ -29,3 +29,9 @@ func (balancer LoadBalancers) Nuke(session *session.Session) error { return nil } + +type ElbDeleteError struct{} + +func (e ElbDeleteError) Error() string { + return "ELB was not deleted" +}