From 2509e97839a99c2e192cbfedef41085a456da297 Mon Sep 17 00:00:00 2001 From: Erik Kristensen Date: Wed, 17 Nov 2021 15:11:03 -0700 Subject: [PATCH 1/2] support deleting cloudwatch log groups --- aws/aws.go | 15 +++++++++ aws/cloudwatch_loggroup.go | 54 ++++++++++++++++++++++++++++++++ aws/cloudwatch_loggroup_types.go | 36 +++++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 aws/cloudwatch_loggroup.go create mode 100644 aws/cloudwatch_loggroup_types.go diff --git a/aws/aws.go b/aws/aws.go index a01d4e63..5d220d1b 100644 --- a/aws/aws.go +++ b/aws/aws.go @@ -587,6 +587,20 @@ func GetAllResources(targetRegions []string, excludeAfter time.Time, resourceTyp } // End CloudWatchDashboard + // CloudWatchLogGroup + cloudwatchLogGroups := CloudWatchLogGroups{} + if IsNukeable(cloudwatchLogGroups.ResourceName(), resourceTypes) { + lgNames, err := getAllCloudWatchLogGroups(session, region) + if err != nil { + return nil, errors.WithStackTrace(err) + } + if len(lgNames) > 0 { + cloudwatchLogGroups.Names = awsgo.StringValueSlice(lgNames) + resourcesInRegion.Resources = append(resourcesInRegion.Resources, cloudwatchLogGroups) + } + } + // End CloudWatchLogGroup + // S3 Buckets s3Buckets := S3Buckets{} if IsNukeable(s3Buckets.ResourceName(), resourceTypes) { @@ -734,6 +748,7 @@ func ListResourceTypes() []string { AccessAnalyzer{}.ResourceName(), DynamoDB{}.ResourceName(), EC2VPCs{}.ResourceName(), + CloudWatchLogGroups{}.ResourceName(), } sort.Strings(resourceTypes) return resourceTypes diff --git a/aws/cloudwatch_loggroup.go b/aws/cloudwatch_loggroup.go new file mode 100644 index 00000000..4d2a3b49 --- /dev/null +++ b/aws/cloudwatch_loggroup.go @@ -0,0 +1,54 @@ +package aws + +import ( + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/cloudwatchlogs" + "github.com/gruntwork-io/cloud-nuke/logging" + "github.com/gruntwork-io/go-commons/errors" +) + +func getAllCloudWatchLogGroups(session *session.Session, region string) ([]*string, error) { + svc := cloudwatchlogs.New(session) + + output, err := svc.DescribeLogGroups(&cloudwatchlogs.DescribeLogGroupsInput{}) + if err != nil { + return nil, errors.WithStackTrace(err) + } + + var names []*string + for _, loggroup := range output.LogGroups { + names = append(names, loggroup.LogGroupName) + } + + return names, nil +} + +func nukeAllCloudWatchLogGroups(session *session.Session, identifiers []*string) error { + svc := cloudwatchlogs.New(session) + + if len(identifiers) == 0 { + logging.Logger.Infof("No CloudWatch Log Groups to nuke in region %s", *session.Config.Region) + return nil + } + + logging.Logger.Infof("Deleting all CloudWatch Log Groups in region %s", *session.Config.Region) + + var deleteResources = 0 + + for _, name := range identifiers { + _, err := svc.DeleteLogGroup(&cloudwatchlogs.DeleteLogGroupInput{ + LogGroupName: name, + }) + if err != nil { + logging.Logger.Errorf("[Failed] %s", err) + } else { + logging.Logger.Infof("[OK] CloudWatch Log Group %s terminated in %s", *name, *session.Config.Region) + deleteResources++ + } + + } + + logging.Logger.Infof("[OK] %d CloudWatch Log Group(s) terminated in %s", deleteResources, *session.Config.Region) + + return nil +} diff --git a/aws/cloudwatch_loggroup_types.go b/aws/cloudwatch_loggroup_types.go new file mode 100644 index 00000000..c38f0601 --- /dev/null +++ b/aws/cloudwatch_loggroup_types.go @@ -0,0 +1,36 @@ +package aws + +import ( + awsgo "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/gruntwork-io/go-commons/errors" +) + +// CloudWatchLogGroup - represents all ec2 instances +type CloudWatchLogGroups struct { + Names []string +} + +// ResourceName - the simple name of the aws resource +func (r CloudWatchLogGroups) ResourceName() string { + return "cloudwatch-loggroup" +} + +// ResourceIdentifiers - The instance ids of the ec2 instances +func (r CloudWatchLogGroups) ResourceIdentifiers() []string { + return r.Names +} + +func (r CloudWatchLogGroups) MaxBatchSize() int { + // Tentative batch size to ensure AWS doesn't throttle + return 200 +} + +// Nuke - nuke 'em all!!! +func (r CloudWatchLogGroups) Nuke(session *session.Session, identifiers []string) error { + if err := nukeAllCloudWatchLogGroups(session, awsgo.StringSlice(identifiers)); err != nil { + return errors.WithStackTrace(err) + } + + return nil +} From d232eeceb55ba9f5827b5f24f7ed705a6ac74ccf Mon Sep 17 00:00:00 2001 From: Erik Kristensen Date: Wed, 17 Nov 2021 15:54:10 -0700 Subject: [PATCH 2/2] updating README docs and adding tests --- README.md | 1 + aws/cloudwatch_loggroup_test.go | 121 ++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 aws/cloudwatch_loggroup_test.go diff --git a/README.md b/README.md index 08db2aa7..5d4869c6 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ The currently supported functionality includes: - Deleting all DynamoDB tables in an AWS account - Deleting all CloudWatch Dashboards in an AWS account - Deleting all OpenSearch Domains in an AWS account +- Deleting all CloudWatch Log Groups in an AWS Account ### BEWARE! diff --git a/aws/cloudwatch_loggroup_test.go b/aws/cloudwatch_loggroup_test.go new file mode 100644 index 00000000..9999a487 --- /dev/null +++ b/aws/cloudwatch_loggroup_test.go @@ -0,0 +1,121 @@ +package aws + +import ( + "fmt" + "strings" + "testing" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/cloudwatchlogs" + "github.com/gruntwork-io/cloud-nuke/util" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestListCloudWatchLogGroups(t *testing.T) { + t.Parallel() + + region, err := getRandomRegion() + require.NoError(t, err) + + session, err := session.NewSession(&aws.Config{Region: aws.String(region)}) + require.NoError(t, err) + + svc := cloudwatchlogs.New(session) + + lgName := createCloudWatchLogGroup(t, svc, region) + defer deleteCloudWatchLogGroup(t, svc, lgName, true) + + lgNames, err := getAllCloudWatchLogGroups(session, region) + require.NoError(t, err) + assert.Contains(t, aws.StringValueSlice(lgNames), aws.StringValue(lgName)) +} + +func TestNukeCloudWatchLogGroupOne(t *testing.T) { + t.Parallel() + + region, err := getRandomRegion() + require.NoError(t, err) + + session, err := session.NewSession(&aws.Config{Region: aws.String(region)}) + require.NoError(t, err) + svc := cloudwatchlogs.New(session) + + // We ignore errors in the delete call here, because it is intended to be a stop gap in case there is a bug in nuke. + lgName := createCloudWatchLogGroup(t, svc, region) + defer deleteCloudWatchLogGroup(t, svc, lgName, false) + identifiers := []*string{lgName} + + require.NoError( + t, + nukeAllCloudWatchLogGroups(session, identifiers), + ) + + // Make sure the CloudWatch Dashboard is deleted. + assertCloudWatchLogGroupsDeleted(t, svc, identifiers) +} + +func TestNukeCloudWatchLogGroupMoreThanOne(t *testing.T) { + t.Parallel() + + region, err := getRandomRegion() + require.NoError(t, err) + + session, err := session.NewSession(&aws.Config{Region: aws.String(region)}) + require.NoError(t, err) + svc := cloudwatchlogs.New(session) + + lgNames := []*string{} + for i := 0; i < 3; i++ { + // We ignore errors in the delete call here, because it is intended to be a stop gap in case there is a bug in nuke. + lgName := createCloudWatchLogGroup(t, svc, region) + defer deleteCloudWatchLogGroup(t, svc, lgName, false) + lgNames = append(lgNames, lgName) + } + + require.NoError( + t, + nukeAllCloudWatchLogGroups(session, lgNames), + ) + + // Make sure the CloudWatch Dashboard is deleted. + assertCloudWatchLogGroupsDeleted(t, svc, lgNames) +} + +func createCloudWatchLogGroup(t *testing.T, svc *cloudwatchlogs.CloudWatchLogs, region string) *string { + uniqueID := util.UniqueID() + name := fmt.Sprintf("cloud-nuke-test-%s", strings.ToLower(uniqueID)) + + _, err := svc.CreateLogGroup(&cloudwatchlogs.CreateLogGroupInput{ + LogGroupName: aws.String(name), + }) + require.NoError(t, err) + + // Add an arbitrary sleep to account for eventual consistency + time.Sleep(15 * time.Second) + return &name +} + +// deleteCloudWatchLogGroup is a function to delete the given CloudWatch Log Group. +func deleteCloudWatchLogGroup(t *testing.T, svc *cloudwatchlogs.CloudWatchLogs, name *string, checkErr bool) { + _, err := svc.DeleteLogGroup(&cloudwatchlogs.DeleteLogGroupInput{ + LogGroupName: name, + }) + if checkErr { + require.NoError(t, err) + } +} + +func assertCloudWatchLogGroupsDeleted(t *testing.T, svc *cloudwatchlogs.CloudWatchLogs, identifiers []*string) { + for _, name := range identifiers { + resp, err := svc.DescribeLogGroups(&cloudwatchlogs.DescribeLogGroupsInput{ + LogGroupNamePrefix: name, + }) + require.NoError(t, err) + if len(resp.LogGroups) > 0 { + t.Fatalf("Log Group %s is not deleted", aws.StringValue(name)) + } + } +}