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

Add support for more resources #5

merged 38 commits into from
Feb 7, 2018

Conversation

tonerdo
Copy link
Contributor

@tonerdo tonerdo commented Feb 5, 2018

Adds support for nuking:

  • Auto Scale Groups
  • Elastic Load Balancers (Classic and v2)
  • EBS Volumes

Copy link
Member

@brikis98 brikis98 left a comment

Choose a reason for hiding this comment

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

This looks great. Clear structure, obvious how to add more features, and nice tests 👍

}
}

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

aws/asg_test.go Outdated
assert.Fail(t, errors.WithStackTrace(err).Error())
}

err = svc.WaitUntilGroupNotExists(&autoscaling.DescribeAutoScalingGroupsInput{
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if the nukeAllAutoScalingGroups should do this wait call itself? Otherwise, steps that run after it may not realize the ASGs are still in progress of being deleted, and therefore not work correctly. E.g., If we try to delete EBS volumes immediately after, the ASGs still using those volumes may not yet have terminated.


_, err := svc.DeleteVolume(params)
if err != nil {
// Ignore not found errors, some volumes are deleted along with EC2 Instances
Copy link
Member

Choose a reason for hiding this comment

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

Probably still worth logging that "Volume XXX has already been deleted."

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is done as well

Copy link
Member

Choose a reason for hiding this comment

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

It's probably also a good idea to add a log statement with the ID/name of each EBS volume, instance, ASG, etc. that gets nuked. That'll be very useful for debugging and seeing the progress of the code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Logging has been updated to reflect this

aws/ebs_test.go Outdated
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"),
Copy link
Member

Choose a reason for hiding this comment

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

I just noticed that these tests all hard-code the region. The downside to this is if we have some region-specific bug, we won't catch it.

You can use this method to pick a random one.

aws/ebs_test.go Outdated
// Retrive only IDs of instances with the unique test tag
for _, tag := range volume.Tags {
if *tag.Key == "Name" {
if *tag.Value == name {
Copy link
Member

Choose a reason for hiding this comment

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

Instead of a second level of nesting, you could combine the two if-statements: if *tag.Key == "Name" && *tag.Value == name.

Copy link
Member

Choose a reason for hiding this comment

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

Also, I wonder if there is a chance tag.Key or tag.Value could be a nil pointer? Might be safer to use aws.StringValue.


volumeIds := findEBSVolumesByNameTag(output, uniqueTestID)

if err := nukeAllEbsVolumes(session, volumeIds); err != nil {
Copy link
Member

Choose a reason for hiding this comment

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

Does this method wait for all volumes to be deleted? Or is it possible for it to return while deletion is still in progress, causing the next check to fail?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It does now

aws/ec2.go Outdated
@@ -85,6 +85,20 @@ func nukeAllEc2Instances(session *session.Session, instanceIds []*string) error
return errors.WithStackTrace(err)
}

err = svc.WaitUntilInstanceTerminated(&ec2.DescribeInstancesInput{
Copy link
Member

Choose a reason for hiding this comment

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

Ah, yea, this seems like a good addition. I'd recommend adding equivalent "wait until XXX deleted" to the EBS, ASG, and ELB code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This has been done for everything except classic ELB which doesn't have this functionality

Copy link
Member

Choose a reason for hiding this comment

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

If there's no built-in WaitUntilXXX method, you can create your own! You just create a retry loop (see DoWithRetry), looking up the ELB by name until it no longer exists or max retries is exceeded.

assert.Contains(t, awsgo.StringValueSlice(elbNames), elbName)
}

func TestNukeELBs(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.

How long do the tests take to run? It seems like it would take a while.

You should probably add:

t.Parallel()

As the first line of each test. This will get them all to run in parallel. It will also flush out any concurrency bugs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Tests now run and pass concurrently

assert.Failf(t, "Could not create test ELBv2: %s", errors.WithStackTrace(err).Error())
}

balancer := *result.LoadBalancers[0]
Copy link
Member

Choose a reason for hiding this comment

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

Could this list be empty? Prob worth checking and returning an error if it is.

README.md Outdated
@@ -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` 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:
Copy link
Member

Choose a reason for hiding this comment

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

We might want to call it --exclude-region, as I suspect we'll have other types of excludes in the future (e.g., --exclude-ec2-instances).

aws/asg_test.go Outdated
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{
Copy link
Member

Choose a reason for hiding this comment

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

Should the createTestEC2Instance method do the waiting?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah missed this. Thanks

aws/aws.go Outdated
"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",
Copy link
Member

Choose a reason for hiding this comment

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

Ah, good catch

aws/aws.go Outdated
account := AwsAccountResources{
Resources: make(map[string]AwsRegionResource),
}

for _, region := range getAllRegions() {
// Ignore all cli excluded regions
if collections.ListContainsElement(excludedRegions, region) {
continue
Copy link
Member

Choose a reason for hiding this comment

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

Might be a good idea to log either "Processing region XXX" or "Skipping region XXX".

aws/aws.go Outdated
@@ -39,6 +62,46 @@ func GetAllResources() (*AwsAccountResources, error) {

resourcesInRegion := AwsRegionResource{}

// ASG Names
groupNames, err := getAllAutoScalingGroups(session, region)
Copy link
Member

Choose a reason for hiding this comment

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

You might want to add a comment near the top of this method that the order of these resources matters due to dependencies between them: e.g., ASGs first, then EC2 instances, then EBS volumes.


_, err := svc.DeleteVolume(params)
if err != nil {
// Ignore not found errors, some volumes are deleted along with EC2 Instances
Copy link
Member

Choose a reason for hiding this comment

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

It's probably also a good idea to add a log statement with the ID/name of each EBS volume, instance, ASG, etc. that gets nuked. That'll be very useful for debugging and seeing the progress of the code.

aws/ebs_test.go Outdated
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
Copy link
Member

Choose a reason for hiding this comment

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

s/Retrive/Retrieve/

aws/ec2_test.go Outdated
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))])
func getLinuxAmiIDForRegion(region string) string {
Copy link
Member

Choose a reason for hiding this comment

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

This will be a pain to maintain as regions are added or AMIs removed. You should be able to find the AMI automatically using Amazon's APIs, similar to Packer's source_ami_filter.

commands/cli.go Outdated
@@ -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"))
Copy link
Member

Choose a reason for hiding this comment

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

You should validate the list of regions passed in to ensure each one corresponds to a valid AWS region. We don't want to blow away a region because someone made a typo (e.g., us-wes-1).

aws/asg_test.go Outdated
assert.Contains(t, awsgo.StringValueSlice(groupNames), groupName)

// clean up after this test
defer nukeAllAutoScalingGroups(session, []*string{&groupName})
Copy link
Member

Choose a reason for hiding this comment

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

This should go immediately after the createTestAutoScalingGroup call. Otherwise, one of the assert calls may fail and prevent the code from ever executing this cleanup step.

aws/ebs_test.go Outdated
assert.Contains(t, awsgo.StringValueSlice(volumeIds), awsgo.StringValue(volume.VolumeId))

// clean up after this test
defer nukeAllEbsVolumes(session, []*string{volume.VolumeId})
Copy link
Member

Choose a reason for hiding this comment

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

Same comment about defer here and any other test cases.

aws/ec2_test.go Outdated
if err != nil {
assert.Failf(t, "Could not create test EC2 instance: %s", errors.WithStackTrace(err).Error())
if err != nil || len(runResult.Instances) == 0 {
assert.Fail(t, "Could not create test EC2 instance")
Copy link
Member

Choose a reason for hiding this comment

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

You may want to include err and runResult.Instances in the log statement so we know what the issue was.

aws/elb.go Outdated
for i := 0; i < 30; i++ {
_, err := svc.DescribeLoadBalancers(input)
if err != nil {
// an error is returned when ELB no longer exists
Copy link
Member

Choose a reason for hiding this comment

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

You need to check which error! This could be a 500 error or 404 or 403 or a dozen other things, most of which don't mean the ELB is gone. See here for how to check AWS errors.

aws/elb.go Outdated
return nil
}

time.Sleep(1 * time.Second)
Copy link
Member

Choose a reason for hiding this comment

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

Log here (debug) that you're waiting for the EBS volume to be deleted so it's clear what's going on.

aws/elb.go Outdated
time.Sleep(1 * time.Second)
}

panic("ELBs failed to delete")
Copy link
Member

Choose a reason for hiding this comment

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

Return an error. Don't panic.

commands/cli.go Outdated
for _, excludedRegion := range excludedRegions {
if !collections.ListContainsElement(regions, excludedRegion) {
fmt.Println(excludedRegion + "is not a valid AWS Region")
return InvalidFlagError{}
Copy link
Member

Choose a reason for hiding this comment

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

Instead of the error message in a println and a separate error with no info, update InvalidFlagError to have the flag name and value as fields and to have an Error() method that clearly shows them to the user.

commands/cli.go Outdated

account, err := aws.GetAllResources()
fmt.Println("Retrieving all active AWS resources")
Copy link
Member

Choose a reason for hiding this comment

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

Use the logger rather than fmt.Println. Applies everywhere in this code.

Copy link
Member

@brikis98 brikis98 left a comment

Choose a reason for hiding this comment

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

Fantastic :-D

@brikis98 brikis98 merged commit 069d76f into master Feb 7, 2018
@tonerdo tonerdo changed the title aws-nuke v0.0.2 Add support for more resources Feb 11, 2018
@tonerdo tonerdo deleted the v0.0.2 branch February 12, 2018 12:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants