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

support deleting cloudwatch log groups #250

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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!

Expand Down
15 changes: 15 additions & 0 deletions aws/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -734,6 +748,7 @@ func ListResourceTypes() []string {
AccessAnalyzer{}.ResourceName(),
DynamoDB{}.ResourceName(),
EC2VPCs{}.ResourceName(),
CloudWatchLogGroups{}.ResourceName(),
}
sort.Strings(resourceTypes)
return resourceTypes
Expand Down
54 changes: 54 additions & 0 deletions aws/cloudwatch_loggroup.go
Original file line number Diff line number Diff line change
@@ -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
}
121 changes: 121 additions & 0 deletions aws/cloudwatch_loggroup_test.go
Original file line number Diff line number Diff line change
@@ -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 {
Copy link
Member

Choose a reason for hiding this comment

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

region not used

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))
}
}
}
36 changes: 36 additions & 0 deletions aws/cloudwatch_loggroup_types.go
Original file line number Diff line number Diff line change
@@ -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
}