Skip to content

Commit

Permalink
Merge pull request #19 from gruntwork-io/snapshot-nuking
Browse files Browse the repository at this point in the history
Snapshot nuking
  • Loading branch information
tonerdo authored Mar 15, 2018
2 parents e95ced3 + c3b7425 commit ddf11be
Show file tree
Hide file tree
Showing 6 changed files with 219 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The currently supported functionality includes:
* Deleting all EBS Volumes in an AWS account
* Deleting all unprotected EC2 instances in an AWS account
* Deleting all AMIs in an AWS account
* Deleting all Snapshots 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 Down
13 changes: 13 additions & 0 deletions aws/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,19 @@ func GetAllResources(regions []string, excludedRegions []string, excludeAfter ti
resourcesInRegion.Resources = append(resourcesInRegion.Resources, amis)
// End AMIs

// Snapshots
snapshotIds, err := getAllSnapshots(session, region, excludeAfter)
if err != nil {
return nil, errors.WithStackTrace(err)
}

snapshots := Snapshots{
SnapshotIds: awsgo.StringValueSlice(snapshotIds),
}

resourcesInRegion.Resources = append(resourcesInRegion.Resources, snapshots)
// End Snapshots

account.Resources[region] = resourcesInRegion
}

Expand Down
63 changes: 63 additions & 0 deletions aws/snapshot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package aws

import (
"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/logging"
"github.com/gruntwork-io/gruntwork-cli/errors"
)

// Returns a formatted string of Snapshot snapshot ids
func getAllSnapshots(session *session.Session, region string, excludeAfter time.Time) ([]*string, error) {
svc := ec2.New(session)

params := &ec2.DescribeSnapshotsInput{
OwnerIds: []*string{awsgo.String("self")},
}

output, err := svc.DescribeSnapshots(params)
if err != nil {
return nil, errors.WithStackTrace(err)
}

var snapshotIds []*string
for _, snapshot := range output.Snapshots {
if excludeAfter.After(*snapshot.StartTime) {
snapshotIds = append(snapshotIds, snapshot.SnapshotId)
}
}

return snapshotIds, nil
}

// Deletes all Snapshots
func nukeAllSnapshots(session *session.Session, snapshotIds []*string) error {
svc := ec2.New(session)

if len(snapshotIds) == 0 {
logging.Logger.Infof("No Snapshots to nuke in region %s", *session.Config.Region)
return nil
}

logging.Logger.Infof("Terminating all Snapshots in region %s", *session.Config.Region)

for _, snapshotID := range snapshotIds {
params := &ec2.DeleteSnapshotInput{
SnapshotId: snapshotID,
}

_, err := svc.DeleteSnapshot(params)
if err != nil {
logging.Logger.Errorf("[Failed] %s", err)
return errors.WithStackTrace(err)
}

logging.Logger.Infof("Deleted Snapshot: %s", *snapshotID)
}

logging.Logger.Infof("[OK] %d Snapshot(s) terminated in %s", len(snapshotIds), *session.Config.Region)
return nil
}
111 changes: 111 additions & 0 deletions aws/snapshot_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package aws

import (
"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"
)

func createTestSnapshot(t *testing.T, session *session.Session, name string) ec2.Snapshot {
svc := ec2.New(session)

az := awsgo.StringValue(session.Config.Region) + "a"
volume := createTestEBSVolume(t, session, name, az)
snapshot, err := svc.CreateSnapshot(&ec2.CreateSnapshotInput{
VolumeId: volume.VolumeId,
})

if err != nil {
assert.Failf(t, "Could not create test Snapshot", errors.WithStackTrace(err).Error())
}

err = svc.WaitUntilSnapshotCompleted(&ec2.DescribeSnapshotsInput{
OwnerIds: []*string{awsgo.String("self")},
SnapshotIds: []*string{snapshot.SnapshotId},
})

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

return *snapshot
}

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

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

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

uniqueTestID := "aws-nuke-test-" + util.UniqueID()
snapshot := createTestSnapshot(t, session, uniqueTestID)

// clean up after this test
defer nukeAllSnapshots(session, []*string{snapshot.SnapshotId})
defer nukeAllEbsVolumes(session, findEBSVolumesByNameTag(t, session, uniqueTestID))

snapshots, err := getAllSnapshots(session, region, time.Now().Add(1*time.Hour*-1))
if err != nil {
assert.Fail(t, "Unable to fetch list of Snapshots")
}

assert.NotContains(t, awsgo.StringValueSlice(snapshots), *snapshot.SnapshotId)

snapshots, err = getAllSnapshots(session, region, time.Now().Add(1*time.Hour))
if err != nil {
assert.Fail(t, "Unable to fetch list of Snapshots")
}

assert.Contains(t, awsgo.StringValueSlice(snapshots), *snapshot.SnapshotId)
}

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

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

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

uniqueTestID := "aws-nuke-test-" + util.UniqueID()
snapshot := createTestSnapshot(t, session, uniqueTestID)

// clean up ec2 instance created by the above call
defer nukeAllEbsVolumes(session, findEBSVolumesByNameTag(t, session, uniqueTestID))

_, err = svc.DescribeSnapshots(&ec2.DescribeSnapshotsInput{
SnapshotIds: []*string{snapshot.SnapshotId},
})

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

if err := nukeAllSnapshots(session, []*string{snapshot.SnapshotId}); err != nil {
assert.Fail(t, errors.WithStackTrace(err).Error())
}

snapshots, err := getAllSnapshots(session, region, time.Now().Add(1*time.Hour))
if err != nil {
assert.Fail(t, "Unable to fetch list of Snapshots")
}

assert.NotContains(t, awsgo.StringValueSlice(snapshots), *snapshot.SnapshotId)
}
30 changes: 30 additions & 0 deletions aws/snapshot_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
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"
)

// Snapshots - represents all user owned Snapshots
type Snapshots struct {
SnapshotIds []string
}

// ResourceName - the simple name of the aws resource
func (snapshot Snapshots) ResourceName() string {
return "snap"
}

// ResourceIdentifiers - The Snapshot snapshot ids
func (snapshot Snapshots) ResourceIdentifiers() []string {
return snapshot.SnapshotIds
}

// Nuke - nuke 'em all!!!
func (snapshot Snapshots) Nuke(session *session.Session) error {
if err := nukeAllSnapshots(session, awsgo.StringSlice(snapshot.SnapshotIds)); err != nil {
return errors.WithStackTrace(err)
}
return nil
}
2 changes: 1 addition & 1 deletion commands/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func CreateCli(version string) *cli.App {
app.HelpName = app.Name
app.Author = "Gruntwork <www.gruntwork.io>"
app.Version = version
app.Usage = "A CLI tool to cleanup AWS resources (ASG, ELB, ELBv2, EBS, EC2, AMI). 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, AMI, Snapshots). THIS TOOL WILL COMPLETELY REMOVE ALL RESOURCES AND ITS EFFECTS ARE IRREVERSIBLE!!!"
app.Flags = []cli.Flag{
cli.StringSliceFlag{
Name: "exclude-region",
Expand Down

0 comments on commit ddf11be

Please sign in to comment.