Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add support for more resources #5

Merged
merged 38 commits into from
Feb 7, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
e84d0e4
Move EC2Instances type to ec2 specific types file
tonerdo Jan 29, 2018
9649bae
Add functionality to nuke Auto scaling groups
tonerdo Jan 29, 2018
feebc33
Include ASGs in list of supported AWS resources
tonerdo Jan 29, 2018
56101fa
Add tests for Auto Scaling Group nuking functionality
tonerdo Jan 29, 2018
4d683b1
add functionality to nuke elastic load balancers
tonerdo Feb 1, 2018
307d97b
add functionality to nuke V2 elastic load balancers
tonerdo Feb 1, 2018
8bf39d3
add information about deleting load balancers
tonerdo Feb 1, 2018
163170d
add functionality to nuke elastic block store volumes
tonerdo Feb 1, 2018
7fb51a3
add information about deleting EBS volumes
tonerdo Feb 1, 2018
a29434e
add tests for nuking elb
tonerdo Feb 2, 2018
dcdc88d
ensure ec2 instance is terminated before proceeding
tonerdo Feb 2, 2018
eb82cf8
add tests for nuking of EBS volumes
tonerdo Feb 2, 2018
05f79e4
ignore volume not found errors during nuking of ebs volumes
tonerdo Feb 2, 2018
99c91a5
add test for nuking elbv2
tonerdo Feb 5, 2018
1d5c534
ensure all ASGs have been nuked before nuke function returns
tonerdo Feb 5, 2018
b51e604
update ebs test to guard against nil pointer
tonerdo Feb 5, 2018
c95bad0
ensure elbv2 load balancer is successfully created
tonerdo Feb 5, 2018
fa9ad56
ensure ebs volume is deleted before returning from nuke method
tonerdo Feb 5, 2018
d7cd7ec
ensure elbv2 is deleted before returning from nuke function
tonerdo Feb 5, 2018
e8d7c5a
Run all tests in parallel
tonerdo Feb 5, 2018
69b32f3
add funtion to return a random region and ignore reserved regions
tonerdo Feb 5, 2018
ab693b0
use random regions for tests
tonerdo Feb 5, 2018
a4048ef
add function to return a linux ami for a specific region
tonerdo Feb 6, 2018
e913e17
Move unique id function to separate go package file
tonerdo Feb 6, 2018
90dd2c7
clean up resources created in list tests
tonerdo Feb 6, 2018
2ceecca
add ability to exclude resources in certain regions from being deleted
tonerdo Feb 6, 2018
5d1af22
change name of exclude region flag
tonerdo Feb 6, 2018
a3c38be
fix typo
tonerdo Feb 6, 2018
88b5c0f
validate excluded regions
tonerdo Feb 6, 2018
6187bab
createTestEC2Instance should wait for created instance to be in the r…
tonerdo Feb 6, 2018
8bf36f9
improve logging and overall cli UX
tonerdo Feb 6, 2018
f18e711
wait until elb is deleted before returning from nuke function
tonerdo Feb 7, 2018
d6ce82c
use dynamically selected images to create ec2 instance
tonerdo Feb 7, 2018
146c760
re-order cleanup defer calls
tonerdo Feb 7, 2018
e368ad4
use the logger package to print log all info
tonerdo Feb 7, 2018
b010c07
update InvalidFlagError to contain more info
tonerdo Feb 7, 2018
9169791
distinguish between ec2 creation errors
tonerdo Feb 7, 2018
fa51aa5
update waitUntilElbDeleted function to only return success on LoadBal…
tonerdo Feb 7, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +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 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
Expand All @@ -19,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-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-region ap-south-1 --exclude-region ap-south-2
```

Happy Nuking!!!

## Credentials
Expand Down
63 changes: 63 additions & 0 deletions aws/asg.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
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("Deleted Auto Scaling Group: %s", *groupName)
}

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
}
98 changes: 98 additions & 0 deletions aws/asg_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
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/gruntwork-io/aws-nuke/util"
"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)

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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love the tests!

One issue: do the tests clean up after themselves? Or do they leave the instances / ASGs running? You should probably add a defer to each test that runs the appropriate clean up functions you already have in this codebase.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typically I run aws-nuke to clean up the resources the tests leave around. I feel it's a good exercise of the functionality of the tool

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, we'll be hooking up the tests to run automatically after every commit to the aws-nuke repo, so we can't rely on manual cleanup. I'd rather have the tests make an effort to clean up after themselves and we'll have a scheduled run of aws-nuke in our AWS accounts.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

List tests now cleanup after themselves

t.Parallel()

region := getRandomRegion()
session, err := session.NewSession(&awsgo.Config{
Region: awsgo.String(region)},
)

if err != nil {
assert.Fail(t, errors.WithStackTrace(err).Error())
}

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 {
assert.Fail(t, "Unable to fetch list of Auto Scaling Groups")
}

assert.Contains(t, awsgo.StringValueSlice(groupNames), groupName)
}

func TestNukeAutoScalingGroups(t *testing.T) {
t.Parallel()

region := getRandomRegion()
session, err := session.NewSession(&awsgo.Config{
Region: awsgo.String(region)},
)
svc := autoscaling.New(session)

if err != nil {
assert.Fail(t, errors.WithStackTrace(err).Error())
}

groupName := "aws-nuke-test-" + util.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())
}

groupNames, err := getAllAutoScalingGroups(session, region)
if err != nil {
assert.Fail(t, "Unable to fetch list of Auto Scaling Groups")
}

assert.NotContains(t, awsgo.StringValueSlice(groupNames), groupName)
}
31 changes: 31 additions & 0 deletions aws/asg_types.go
Original file line number Diff line number Diff line change
@@ -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 auto scaling groups
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
}
97 changes: 90 additions & 7 deletions aws/aws.go
Original file line number Diff line number Diff line change
@@ -1,34 +1,59 @@
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/aws-nuke/logging"
"github.com/gruntwork-io/gruntwork-cli/collections"
"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",
}

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) {
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) {
logging.Logger.Infoln("Skipping region: " + region)
continue
}

session, err := session.NewSession(&awsgo.Config{
Region: awsgo.String(region)},
)
Expand All @@ -39,6 +64,49 @@ func GetAllResources() (*AwsAccountResources, error) {

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 {
return nil, errors.WithStackTrace(err)
}

asGroups := ASGroups{
GroupNames: awsgo.StringValueSlice(groupNames),
}

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

// 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 {
return nil, errors.WithStackTrace(err)
Expand All @@ -49,15 +117,30 @@ func GetAllResources() (*AwsAccountResources, error) {
}

resourcesInRegion.Resources = append(resourcesInRegion.Resources, ec2Instances)
// End EC2 Instances

// 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

account.Resources[region] = resourcesInRegion
}

return &account, nil
}

// 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)},
)
Expand Down
Loading