From 0ece707675b3c28cbb38e4f447ef1b46e3241b71 Mon Sep 17 00:00:00 2001 From: Max Moon Date: Tue, 11 Apr 2023 09:19:34 -0700 Subject: [PATCH] chore(CORE-659): Improve filtering for EBS Volumes and Snapshots (#439) * Add filter to only list available and creating ebs volumes * Add filter to only list completed ebs snapshots that are not managed by AWS backup --- aws/ebs.go | 13 +++++++++++-- aws/ebs_test.go | 24 ++++++++++++++++++++++-- aws/snapshot.go | 28 ++++++++++++++++++++++++++-- 3 files changed, 59 insertions(+), 6 deletions(-) diff --git a/aws/ebs.go b/aws/ebs.go index 62a6ba63..2e5bad02 100644 --- a/aws/ebs.go +++ b/aws/ebs.go @@ -1,9 +1,10 @@ package aws import ( + "time" + "github.com/gruntwork-io/cloud-nuke/telemetry" commonTelemetry "github.com/gruntwork-io/go-commons/telemetry" - "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" @@ -19,7 +20,15 @@ import ( func getAllEbsVolumes(session *session.Session, region string, excludeAfter time.Time, configObj config.Config) ([]*string, error) { svc := ec2.New(session) - result, err := svc.DescribeVolumes(&ec2.DescribeVolumesInput{}) + // Available statuses: (creating | available | in-use | deleting | deleted | error). + // Since the output of this function is used to delete the returned volumes + // We want to only list EBS volumes with a status of "available" or "creating" + // Since those are the only statuses that are eligible for deletion + statusFilter := ec2.Filter{Name: aws.String("status"), Values: aws.StringSlice([]string{"available", "creating", "error"})} + + result, err := svc.DescribeVolumes(&ec2.DescribeVolumesInput{ + Filters: []*ec2.Filter{&statusFilter}, + }) if err != nil { return nil, errors.WithStackTrace(err) } diff --git a/aws/ebs_test.go b/aws/ebs_test.go index 77600c66..29320f76 100644 --- a/aws/ebs_test.go +++ b/aws/ebs_test.go @@ -1,11 +1,12 @@ package aws import ( - "github.com/gruntwork-io/cloud-nuke/telemetry" "regexp" "testing" "time" + "github.com/gruntwork-io/cloud-nuke/telemetry" + "github.com/aws/aws-sdk-go/aws" awsgo "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" @@ -85,6 +86,24 @@ func findEBSVolumesByNameTag(t *testing.T, session *session.Session, name string return volumeIds } +func findallEBSVolumesByStatus(t *testing.T, session *session.Session, status string) ([]*string, error) { + statusFilter := ec2.Filter{Name: aws.String("status"), Values: aws.StringSlice([]string{status})} + + output, err := ec2.New(session).DescribeVolumes(&ec2.DescribeVolumesInput{ + Filters: []*ec2.Filter{&statusFilter}, + }) + if err != nil { + return nil, err + } + + var volumeIds []*string + for _, volume := range output.Volumes { + volumeIds = append(volumeIds, volume.VolumeId) + } + + return volumeIds, nil +} + func TestListEBSVolumes(t *testing.T) { telemetry.InitTelemetry("cloud-nuke", "", "") t.Parallel() @@ -250,8 +269,9 @@ func TestNukeEBSVolumesInUse(t *testing.T) { assert.Fail(t, errors.WithStackTrace(err).Error()) } - volumeIds, err = getAllEbsVolumes(session, region, time.Now().Add(1*time.Hour), config.Config{}) + volumeIds, err = findallEBSVolumesByStatus(t, session, "in-use") if err != nil { + assert.Fail(t, errors.WithStackTrace(err).Error()) assert.Fail(t, "Unable to fetch list of EBS Volumes") } diff --git a/aws/snapshot.go b/aws/snapshot.go index 2f434b32..025aab0b 100644 --- a/aws/snapshot.go +++ b/aws/snapshot.go @@ -1,9 +1,10 @@ package aws import ( + "time" + "github.com/gruntwork-io/cloud-nuke/telemetry" commonTelemetry "github.com/gruntwork-io/go-commons/telemetry" - "time" "github.com/aws/aws-sdk-go/aws" awsgo "github.com/aws/aws-sdk-go/aws" @@ -18,8 +19,15 @@ import ( func getAllSnapshots(session *session.Session, region string, excludeAfter time.Time) ([]*string, error) { svc := ec2.New(session) + // status - The status of the snapshot (pending | completed | error). + // Since the output of this function is used to delete the returned snapshots + // We only want to list EBS Snapshots with a status of "completed" + // Since that is the only status that is eligible for deletion + status_filter := ec2.Filter{Name: awsgo.String("status"), Values: aws.StringSlice([]string{"completed", "error"})} + params := &ec2.DescribeSnapshotsInput{ OwnerIds: []*string{awsgo.String("self")}, + Filters: []*ec2.Filter{&status_filter}, } output, err := svc.DescribeSnapshots(params) @@ -29,7 +37,7 @@ func getAllSnapshots(session *session.Session, region string, excludeAfter time. var snapshotIds []*string for _, snapshot := range output.Snapshots { - if excludeAfter.After(*snapshot.StartTime) { + if excludeAfter.After(*snapshot.StartTime) && !SnapshotHasAWSBackupTag(snapshot.Tags) { snapshotIds = append(snapshotIds, snapshot.SnapshotId) } } @@ -37,6 +45,22 @@ func getAllSnapshots(session *session.Session, region string, excludeAfter time. return snapshotIds, nil } +// Check if the image has an AWS Backup tag +// Resources created by AWS Backup are listed as owned by self, but are actually +// AWS managed resources and cannot be deleted here. +func SnapshotHasAWSBackupTag(tags []*ec2.Tag) bool { + t := make(map[string]string) + + for _, v := range tags { + t[awsgo.StringValue(v.Key)] = awsgo.StringValue(v.Value) + } + + if _, ok := t["aws:backup:source-resource"]; ok { + return true + } + return false +} + // Deletes all Snapshots func nukeAllSnapshots(session *session.Session, snapshotIds []*string) error { svc := ec2.New(session)