diff --git a/aws/aws.go b/aws/aws.go index d7b48676..444f3f82 100644 --- a/aws/aws.go +++ b/aws/aws.go @@ -1137,7 +1137,7 @@ func GetAllResources(targetRegions []string, excludeAfter time.Time, resourceTyp } if IsNukeable(cloudwatchDashboards.ResourceName(), resourceTypes) { start := time.Now() - cwdbNames, err := getAllCloudWatchDashboards(cloudNukeSession, excludeAfter, configObj) + cwdbNames, err := cloudwatchDashboards.getAll(configObj) if err != nil { ge := report.GeneralError{ Error: err, @@ -1168,7 +1168,7 @@ func GetAllResources(targetRegions []string, excludeAfter time.Time, resourceTyp } if IsNukeable(cloudwatchLogGroups.ResourceName(), resourceTypes) { start := time.Now() - lgNames, err := getAllCloudWatchLogGroups(cloudNukeSession, excludeAfter, configObj) + lgNames, err := cloudwatchLogGroups.getAll(configObj) if err != nil { ge := report.GeneralError{ Error: err, @@ -1863,7 +1863,7 @@ func GetAllResources(targetRegions []string, excludeAfter time.Time, resourceTyp } if IsNukeable(cloudwatchAlarms.ResourceName(), resourceTypes) { start := time.Now() - cwalNames, err := getAllCloudWatchAlarms(cloudNukeSession, excludeAfter, configObj) + cwalNames, err := cloudwatchAlarms.getAll(configObj) if err != nil { ge := report.GeneralError{ Error: err, diff --git a/aws/cloudwatch_alarm.go b/aws/cloudwatch_alarm.go index 7a829a9f..0fbfa516 100644 --- a/aws/cloudwatch_alarm.go +++ b/aws/cloudwatch_alarm.go @@ -2,35 +2,37 @@ package aws import ( "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/cloudwatch" + "github.com/gruntwork-io/cloud-nuke/config" "github.com/gruntwork-io/cloud-nuke/logging" "github.com/gruntwork-io/cloud-nuke/report" "github.com/gruntwork-io/cloud-nuke/telemetry" "github.com/gruntwork-io/go-commons/errors" commonTelemetry "github.com/gruntwork-io/go-commons/telemetry" - "time" - - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/cloudwatch" - "github.com/gruntwork-io/cloud-nuke/config" ) -func getAllCloudWatchAlarms(session *session.Session, excludeAfter time.Time, configObj config.Config) ([]*string, error) { - svc := cloudwatch.New(session) - +func (cw CloudWatchAlarms) getAll(configObj config.Config) ([]*string, error) { allAlarms := []*string{} input := &cloudwatch.DescribeAlarmsInput{ AlarmTypes: aws.StringSlice([]string{cloudwatch.AlarmTypeMetricAlarm, cloudwatch.AlarmTypeCompositeAlarm}), } - err := svc.DescribeAlarmsPages( + err := cw.Client.DescribeAlarmsPages( input, func(page *cloudwatch.DescribeAlarmsOutput, lastPage bool) bool { for _, alarm := range page.MetricAlarms { - if shouldIncludeCloudWatchMetricAlarm(alarm, excludeAfter, configObj) { + if configObj.CloudWatchAlarm.ShouldInclude(config.ResourceValue{ + Name: alarm.AlarmName, + Time: alarm.AlarmConfigurationUpdatedTimestamp, + }) { allAlarms = append(allAlarms, alarm.AlarmName) } } + for _, alarm := range page.CompositeAlarms { - if shouldIncludeCloudWatchCompositeAlarm(alarm, excludeAfter, configObj) { + if configObj.CloudWatchAlarm.ShouldInclude(config.ResourceValue{ + Name: alarm.AlarmName, + Time: alarm.AlarmConfigurationUpdatedTimestamp, + }) { allAlarms = append(allAlarms, alarm.AlarmName) } } @@ -40,45 +42,9 @@ func getAllCloudWatchAlarms(session *session.Session, excludeAfter time.Time, co return allAlarms, errors.WithStackTrace(err) } -func shouldIncludeCloudWatchCompositeAlarm(alarm *cloudwatch.CompositeAlarm, excludeAfter time.Time, configObj config.Config) bool { - if alarm == nil { - return false - } - - if alarm.AlarmConfigurationUpdatedTimestamp != nil && excludeAfter.Before(*alarm.AlarmConfigurationUpdatedTimestamp) { - return false - } - - return config.ShouldInclude( - aws.StringValue(alarm.AlarmName), - configObj.CloudWatchAlarm.IncludeRule.NamesRegExp, - configObj.CloudWatchAlarm.ExcludeRule.NamesRegExp, - ) -} - -func shouldIncludeCloudWatchMetricAlarm(alarm *cloudwatch.MetricAlarm, excludeAfter time.Time, configObj config.Config) bool { - if alarm == nil { - return false - } - - if alarm.AlarmConfigurationUpdatedTimestamp != nil && excludeAfter.Before(*alarm.AlarmConfigurationUpdatedTimestamp) { - return false - } - - return config.ShouldInclude( - aws.StringValue(alarm.AlarmName), - configObj.CloudWatchAlarm.IncludeRule.NamesRegExp, - configObj.CloudWatchAlarm.ExcludeRule.NamesRegExp, - ) -} - -func nukeAllCloudWatchAlarms(session *session.Session, identifiers []*string) error { - region := aws.StringValue(session.Config.Region) - - svc := cloudwatch.New(session) - +func (cw CloudWatchAlarms) nukeAll(identifiers []*string) error { if len(identifiers) == 0 { - logging.Logger.Debugf("No CloudWatch Alarms to nuke in region %s", region) + logging.Logger.Debugf("No CloudWatch Alarms to nuke in region %s", cw.Region) return nil } @@ -91,10 +57,10 @@ func nukeAllCloudWatchAlarms(session *session.Session, identifiers []*string) er return TooManyCloudWatchAlarmsErr{} } - logging.Logger.Debugf("Deleting CloudWatch Alarms in region %s", region) + logging.Logger.Debugf("Deleting CloudWatch Alarms in region %s", cw.Region) // If the alarm's type is composite alarm, remove the dependency by removing the rule. - alarms, err := svc.DescribeAlarms(&cloudwatch.DescribeAlarmsInput{ + alarms, err := cw.Client.DescribeAlarms(&cloudwatch.DescribeAlarmsInput{ AlarmTypes: aws.StringSlice([]string{cloudwatch.AlarmTypeMetricAlarm, cloudwatch.AlarmTypeCompositeAlarm}), AlarmNames: identifiers, }) @@ -103,12 +69,12 @@ func nukeAllCloudWatchAlarms(session *session.Session, identifiers []*string) er telemetry.TrackEvent(commonTelemetry.EventContext{ EventName: "Error Nuking Cloudwatch Alarm Dependency", }, map[string]interface{}{ - "region": *session.Config.Region, + "region": cw.Region, }) } for _, compositeAlarm := range alarms.CompositeAlarms { - _, err := svc.PutCompositeAlarm(&cloudwatch.PutCompositeAlarmInput{ + _, err := cw.Client.PutCompositeAlarm(&cloudwatch.PutCompositeAlarmInput{ AlarmName: compositeAlarm.AlarmName, AlarmRule: aws.String("FALSE"), }) @@ -117,13 +83,13 @@ func nukeAllCloudWatchAlarms(session *session.Session, identifiers []*string) er telemetry.TrackEvent(commonTelemetry.EventContext{ EventName: "Error Nuking Cloudwatch Composite Alarm", }, map[string]interface{}{ - "region": *session.Config.Region, + "region": cw.Region, }) } } input := cloudwatch.DeleteAlarmsInput{AlarmNames: identifiers} - _, err = svc.DeleteAlarms(&input) + _, err = cw.Client.DeleteAlarms(&input) // Record status of this resource e := report.BatchEntry{ @@ -138,13 +104,13 @@ func nukeAllCloudWatchAlarms(session *session.Session, identifiers []*string) er telemetry.TrackEvent(commonTelemetry.EventContext{ EventName: "Error Nuking Cloudwatch Alarm", }, map[string]interface{}{ - "region": *session.Config.Region, + "region": cw.Region, }) return errors.WithStackTrace(err) } for _, alarmName := range identifiers { - logging.Logger.Debugf("[OK] CloudWatch Alarm %s was deleted in %s", aws.StringValue(alarmName), region) + logging.Logger.Debugf("[OK] CloudWatch Alarm %s was deleted in %s", aws.StringValue(alarmName), cw.Region) } return nil } diff --git a/aws/cloudwatch_alarm_test.go b/aws/cloudwatch_alarm_test.go index 005694e7..db282012 100644 --- a/aws/cloudwatch_alarm_test.go +++ b/aws/cloudwatch_alarm_test.go @@ -1,179 +1,114 @@ package aws import ( - "fmt" + "github.com/aws/aws-sdk-go/service/cloudwatch/cloudwatchiface" "github.com/gruntwork-io/cloud-nuke/telemetry" - "strings" + "regexp" "testing" "time" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/cloudwatch" "github.com/gruntwork-io/cloud-nuke/config" - "github.com/gruntwork-io/cloud-nuke/util" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestListCloudWatchAlarms(t *testing.T) { - telemetry.InitTelemetry("cloud-nuke", "") - t.Parallel() - - region, err := getRandomRegion() - require.NoError(t, err) - - session, err := session.NewSession(&aws.Config{Region: aws.String(region)}) - require.NoError(t, err) - svc := cloudwatch.New(session) - - // cwal : CloudWatch ALarms (recommended by ChatGPT) - cwalName := createCloudWatchAlarm(t, svc, region) - defer deleteCloudWatchAlarm(t, svc, cwalName, true) - - cwalNames, err := getAllCloudWatchAlarms(session, time.Now(), config.Config{}) - require.NoError(t, err) - assert.Contains(t, aws.StringValueSlice(cwalNames), aws.StringValue(cwalName)) +type mockedCloudWatchAlarms struct { + cloudwatchiface.CloudWatchAPI + DescribeAlarmsOutput cloudwatch.DescribeAlarmsOutput + DeleteAlarmsOutput cloudwatch.DeleteAlarmsOutput + PutCompositeAlarmOutput cloudwatch.PutCompositeAlarmOutput } -func TestTimeFilterExclusionNewlyCreatedCloudWatchAlarm(t *testing.T) { - telemetry.InitTelemetry("cloud-nuke", "") - t.Parallel() - - region, err := getRandomRegion() - require.NoError(t, err) - - session, err := session.NewSession(&aws.Config{Region: aws.String(region)}) - require.NoError(t, err) - svc := cloudwatch.New(session) - - cwalName := createCloudWatchAlarm(t, svc, region) - defer deleteCloudWatchAlarm(t, svc, cwalName, true) - - // Assert CloudWatch Alarm is picked up without filters - cwalNamesNewer, err := getAllCloudWatchAlarms(session, time.Now(), config.Config{}) - require.NoError(t, err) - assert.Contains(t, aws.StringValueSlice(cwalNamesNewer), aws.StringValue(cwalName)) - - // Assert user doesn't appear when we look at users older than 1 Hour - olderThan := time.Now().Add(-1 * time.Hour) - cwalNamesOlder, err := getAllCloudWatchAlarms(session, olderThan, config.Config{}) - require.NoError(t, err) - assert.NotContains(t, aws.StringValueSlice(cwalNamesOlder), aws.StringValue(cwalName)) +func (m mockedCloudWatchAlarms) DescribeAlarmsPages(input *cloudwatch.DescribeAlarmsInput, fn func(*cloudwatch.DescribeAlarmsOutput, bool) bool) error { + fn(&m.DescribeAlarmsOutput, true) + return nil } -func TestNukeCloudWatchAlarmOne(t *testing.T) { - telemetry.InitTelemetry("cloud-nuke", "") - t.Parallel() - - region, err := getRandomRegion() - require.NoError(t, err) - - session, err := session.NewSession(&aws.Config{Region: aws.String(region)}) - require.NoError(t, err) - svc := cloudwatch.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. - cwalName := createCloudWatchAlarm(t, svc, region) - defer deleteCloudWatchAlarm(t, svc, cwalName, false) - identifiers := []*string{cwalName} +func (m mockedCloudWatchAlarms) PutCompositeAlarm(input *cloudwatch.PutCompositeAlarmInput) (*cloudwatch.PutCompositeAlarmOutput, error) { + return &m.PutCompositeAlarmOutput, nil +} - require.NoError( - t, - nukeAllCloudWatchAlarms(session, identifiers), - ) +func (m mockedCloudWatchAlarms) DescribeAlarms(input *cloudwatch.DescribeAlarmsInput) (*cloudwatch.DescribeAlarmsOutput, error) { + return &m.DescribeAlarmsOutput, nil +} - // Make sure the CloudWatch Alar m is deleted. - assertCloudWatchAlarmsDeleted(t, svc, identifiers) +func (m mockedCloudWatchAlarms) DeleteAlarms(input *cloudwatch.DeleteAlarmsInput) (*cloudwatch.DeleteAlarmsOutput, error) { + return &m.DeleteAlarmsOutput, nil } -func TestNukeCloudWatchAlarmsMoreThanOne(t *testing.T) { +func TestCloudWatchAlarm_GetAll(t *testing.T) { telemetry.InitTelemetry("cloud-nuke", "") t.Parallel() - region, err := getRandomRegion() - require.NoError(t, err) - - session, err := session.NewSession(&aws.Config{Region: aws.String(region)}) - require.NoError(t, err) - svc := cloudwatch.New(session) - - cwalNames := []*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. - cwalName := createCloudWatchAlarm(t, svc, region) - defer deleteCloudWatchAlarm(t, svc, cwalName, false) - cwalNames = append(cwalNames, cwalName) + testName1 := "test-name1" + testName2 := "test-name2" + now := time.Now() + cw := CloudWatchAlarms{ + Client: mockedCloudWatchAlarms{ + DescribeAlarmsOutput: cloudwatch.DescribeAlarmsOutput{ + MetricAlarms: []*cloudwatch.MetricAlarm{ + {AlarmName: aws.String(testName1), AlarmConfigurationUpdatedTimestamp: &now}, + {AlarmName: aws.String(testName2), AlarmConfigurationUpdatedTimestamp: aws.Time(now.Add(1))}, + }}, + }} + + tests := map[string]struct { + configObj config.ResourceType + expected []string + }{ + "emptyFilter": { + configObj: config.ResourceType{}, + expected: []string{testName1, testName2}, + }, + "nameExclusionFilter": { + configObj: config.ResourceType{ + ExcludeRule: config.FilterRule{ + NamesRegExp: []config.Expression{{ + RE: *regexp.MustCompile(testName1), + }}}, + }, + expected: []string{testName2}, + }, + "timeAfterExclusionFilter": { + configObj: config.ResourceType{ + ExcludeRule: config.FilterRule{ + TimeAfter: aws.Time(now.Add(-1)), + }}, + expected: []string{}, + }, } - require.NoError( - t, - nukeAllCloudWatchAlarms(session, cwalNames), - ) - - // Make sure the CloudWatch Alarm is deleted. - assertCloudWatchAlarmsDeleted(t, svc, cwalNames) -} + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + names, err := cw.getAll(config.Config{ + CloudWatchAlarm: tc.configObj, + }) -// Helper functions for driving the CloudWatch Alarm tests - -// createCloudWatchAlarm will create a new CloudWatch Alarm with a simple metric. -func createCloudWatchAlarm(t *testing.T, svc *cloudwatch.CloudWatch, region string) *string { - uniqueID := util.UniqueID() - name := fmt.Sprintf("cloud-nuke-testing-%s", strings.ToLower(uniqueID)) - metric := []*cloudwatch.MetricDataQuery{} - metric = append(metric, &cloudwatch.MetricDataQuery{}) - - _, err := svc.PutMetricAlarm(&cloudwatch.PutMetricAlarmInput{ - ActionsEnabled: aws.Bool(true), - AlarmActions: aws.StringSlice([]string{}), - AlarmDescription: aws.String(`Test alarm for cloud-nuke.`), - AlarmName: aws.String(name), - ComparisonOperator: aws.String(`GreaterThanThreshold`), - DatapointsToAlarm: aws.Int64(1), - Dimensions: []*cloudwatch.Dimension{ - {Name: aws.String(`InstanceId`), Value: aws.String(`i-0123456789abcdefg`)}, - }, - EvaluationPeriods: aws.Int64(60), - InsufficientDataActions: aws.StringSlice([]string{}), - MetricName: aws.String(`CPUUtilization`), - Namespace: aws.String(`AWS/EC2`), - OKActions: aws.StringSlice([]string{}), - Period: aws.Int64(60), - Statistic: aws.String(cloudwatch.StatisticAverage), - Threshold: aws.Float64(0.0), - TreatMissingData: aws.String(`missing`), - }) - require.NoError(t, err) - - // Verify that the alarm is generated well - resp, err := svc.DescribeAlarms(&cloudwatch.DescribeAlarmsInput{ - AlarmNames: []*string{&name}, - }) - require.NoError(t, err) - if len(resp.MetricAlarms) <= 0 { - t.Fatalf("Error creating Alarm %s", name) + require.NoError(t, err) + require.Equal(t, tc.expected, aws.StringValueSlice(names)) + }) } - // Add an arbitrary sleep to account for eventual consistency - time.Sleep(15 * time.Second) - return &name } -// deleteCloudWatchAlarm is a function to delete the given CloudWatch Alarm. -func deleteCloudWatchAlarm(t *testing.T, svc *cloudwatch.CloudWatch, name *string, checkErr bool) { - input := &cloudwatch.DeleteAlarmsInput{AlarmNames: []*string{name}} - _, err := svc.DeleteAlarms(input) - if checkErr { - require.NoError(t, err) - } -} +func TestCloudWatchAlarms_NukeAll(t *testing.T) { + telemetry.InitTelemetry("cloud-nuke", "") + t.Parallel() -func assertCloudWatchAlarmsDeleted(t *testing.T, svc *cloudwatch.CloudWatch, identifiers []*string) { - for _, name := range identifiers { - resp, err := svc.DescribeAlarms(&cloudwatch.DescribeAlarmsInput{AlarmNames: []*string{name}}) - require.NoError(t, err) - if len(resp.MetricAlarms) > 0 { - t.Fatalf("Alarm %s is not deleted", aws.StringValue(name)) - } - } + testName1 := "test-name1" + testName2 := "test-name2" + now := time.Now() + cw := CloudWatchAlarms{ + Client: mockedCloudWatchAlarms{ + DescribeAlarmsOutput: cloudwatch.DescribeAlarmsOutput{ + MetricAlarms: []*cloudwatch.MetricAlarm{ + {AlarmName: aws.String(testName1), AlarmConfigurationUpdatedTimestamp: &now}, + {AlarmName: aws.String(testName2), AlarmConfigurationUpdatedTimestamp: aws.Time(now.Add(1))}, + }}, + PutCompositeAlarmOutput: cloudwatch.PutCompositeAlarmOutput{}, + DeleteAlarmsOutput: cloudwatch.DeleteAlarmsOutput{}, + }} + + err := cw.nukeAll([]*string{aws.String(testName1), aws.String(testName2)}) + require.NoError(t, err) } diff --git a/aws/cloudwatch_alarm_types.go b/aws/cloudwatch_alarm_types.go index 82846811..5d1b029a 100644 --- a/aws/cloudwatch_alarm_types.go +++ b/aws/cloudwatch_alarm_types.go @@ -15,22 +15,22 @@ type CloudWatchAlarms struct { } // ResourceName - the simple name of the aws resource -func (cwal CloudWatchAlarms) ResourceName() string { +func (cw CloudWatchAlarms) ResourceName() string { return "cloudwatch-alarm" } // ResourceIdentifiers - The name of cloudwatch alarms -func (cwal CloudWatchAlarms) ResourceIdentifiers() []string { - return cwal.AlarmNames +func (cw CloudWatchAlarms) ResourceIdentifiers() []string { + return cw.AlarmNames } -func (cwal CloudWatchAlarms) MaxBatchSize() int { +func (cw CloudWatchAlarms) MaxBatchSize() int { return 99 } // Nuke - nuke 'em all!!! -func (cwal CloudWatchAlarms) Nuke(session *session.Session, identifiers []string) error { - if err := nukeAllCloudWatchAlarms(session, awsgo.StringSlice(identifiers)); err != nil { +func (cw CloudWatchAlarms) Nuke(session *session.Session, identifiers []string) error { + if err := cw.nukeAll(awsgo.StringSlice(identifiers)); err != nil { return errors.WithStackTrace(err) } diff --git a/aws/cloudwatch_dashboard.go b/aws/cloudwatch_dashboard.go index 524043c8..67df6b51 100644 --- a/aws/cloudwatch_dashboard.go +++ b/aws/cloudwatch_dashboard.go @@ -1,62 +1,41 @@ package aws import ( - "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/session" "github.com/aws/aws-sdk-go/service/cloudwatch" + "github.com/gruntwork-io/cloud-nuke/telemetry" "github.com/gruntwork-io/go-commons/errors" + commonTelemetry "github.com/gruntwork-io/go-commons/telemetry" "github.com/gruntwork-io/cloud-nuke/config" "github.com/gruntwork-io/cloud-nuke/logging" "github.com/gruntwork-io/cloud-nuke/report" ) -func getAllCloudWatchDashboards(session *session.Session, excludeAfter time.Time, configObj config.Config) ([]*string, error) { - svc := cloudwatch.New(session) - +func (cwdb CloudWatchDashboards) getAll(configObj config.Config) ([]*string, error) { allDashboards := []*string{} input := &cloudwatch.ListDashboardsInput{} - err := svc.ListDashboardsPages( + err := cwdb.Client.ListDashboardsPages( input, func(page *cloudwatch.ListDashboardsOutput, lastPage bool) bool { for _, dashboard := range page.DashboardEntries { - if shouldIncludeCloudWatchDashboard(dashboard, excludeAfter, configObj) { + if configObj.CloudWatchDashboard.ShouldInclude(config.ResourceValue{ + Name: dashboard.DashboardName, + Time: dashboard.LastModified, + }) { allDashboards = append(allDashboards, dashboard.DashboardName) } } + return !lastPage }, ) return allDashboards, errors.WithStackTrace(err) } -func shouldIncludeCloudWatchDashboard(dashboard *cloudwatch.DashboardEntry, excludeAfter time.Time, configObj config.Config) bool { - if dashboard == nil { - return false - } - - if dashboard.LastModified != nil && excludeAfter.Before(*dashboard.LastModified) { - return false - } - - return config.ShouldInclude( - aws.StringValue(dashboard.DashboardName), - configObj.CloudWatchDashboard.IncludeRule.NamesRegExp, - configObj.CloudWatchDashboard.ExcludeRule.NamesRegExp, - ) -} - -func nukeAllCloudWatchDashboards(session *session.Session, identifiers []*string) error { - region := aws.StringValue(session.Config.Region) - - svc := cloudwatch.New(session) - +func (cwdb CloudWatchDashboards) nukeAll(identifiers []*string) error { if len(identifiers) == 0 { - logging.Logger.Debugf("No CloudWatch Dashboards to nuke in region %s", region) + logging.Logger.Debugf("No CloudWatch Dashboards to nuke in region %s", cwdb.Region) return nil } @@ -69,9 +48,9 @@ func nukeAllCloudWatchDashboards(session *session.Session, identifiers []*string return TooManyCloudWatchDashboardsErr{} } - logging.Logger.Debugf("Deleting CloudWatch Dashboards in region %s", region) + logging.Logger.Debugf("Deleting CloudWatch Dashboards in region %s", cwdb.Region) input := cloudwatch.DeleteDashboardsInput{DashboardNames: identifiers} - _, err := svc.DeleteDashboards(&input) + _, err := cwdb.Client.DeleteDashboards(&input) // Record status of this resource e := report.BatchEntry{ @@ -86,13 +65,13 @@ func nukeAllCloudWatchDashboards(session *session.Session, identifiers []*string telemetry.TrackEvent(commonTelemetry.EventContext{ EventName: "Error Nuking Cloudwatch Dashboard", }, map[string]interface{}{ - "region": *session.Config.Region, + "region": cwdb.Region, }) return errors.WithStackTrace(err) } for _, dashboardName := range identifiers { - logging.Logger.Debugf("[OK] CloudWatch Dashboard %s was deleted in %s", aws.StringValue(dashboardName), region) + logging.Logger.Debugf("[OK] CloudWatch Dashboard %s was deleted in %s", aws.StringValue(dashboardName), cwdb.Region) } return nil } diff --git a/aws/cloudwatch_dashboard_test.go b/aws/cloudwatch_dashboard_test.go index 5ef414e6..930f6c13 100644 --- a/aws/cloudwatch_dashboard_test.go +++ b/aws/cloudwatch_dashboard_test.go @@ -1,169 +1,101 @@ package aws import ( - "fmt" + "github.com/aws/aws-sdk-go/service/cloudwatch/cloudwatchiface" "github.com/gruntwork-io/cloud-nuke/telemetry" - "strings" + "regexp" "testing" "time" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/cloudwatch" "github.com/gruntwork-io/cloud-nuke/config" - "github.com/gruntwork-io/cloud-nuke/util" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestListCloudWatchDashboards(t *testing.T) { - telemetry.InitTelemetry("cloud-nuke", "") - t.Parallel() - - region, err := getRandomRegion() - require.NoError(t, err) - - session, err := session.NewSession(&aws.Config{Region: aws.String(region)}) - require.NoError(t, err) - svc := cloudwatch.New(session) - - cwdbName := createCloudWatchDashboard(t, svc, region) - defer deleteCloudWatchDashboard(t, svc, cwdbName, true) - - cwdbNames, err := getAllCloudWatchDashboards(session, time.Now(), config.Config{}) - require.NoError(t, err) - assert.Contains(t, aws.StringValueSlice(cwdbNames), aws.StringValue(cwdbName)) +type mockedCloudWatchDashboard struct { + cloudwatchiface.CloudWatchAPI + ListDashboardsOutput cloudwatch.ListDashboardsOutput + DeleteDashboardsOutput cloudwatch.DeleteDashboardsOutput } -func TestTimeFilterExclusionNewlyCreatedCloudWatchDashboard(t *testing.T) { - telemetry.InitTelemetry("cloud-nuke", "") - t.Parallel() - - region, err := getRandomRegion() - require.NoError(t, err) - - session, err := session.NewSession(&aws.Config{Region: aws.String(region)}) - require.NoError(t, err) - svc := cloudwatch.New(session) - - cwdbName := createCloudWatchDashboard(t, svc, region) - defer deleteCloudWatchDashboard(t, svc, cwdbName, true) - - // Assert CloudWatch Dashboard is picked up without filters - cwdbNamesNewer, err := getAllCloudWatchDashboards(session, time.Now(), config.Config{}) - require.NoError(t, err) - assert.Contains(t, aws.StringValueSlice(cwdbNamesNewer), aws.StringValue(cwdbName)) - - // Assert user doesn't appear when we look at users older than 1 Hour - olderThan := time.Now().Add(-1 * time.Hour) - cwdbNamesOlder, err := getAllCloudWatchDashboards(session, olderThan, config.Config{}) - require.NoError(t, err) - assert.NotContains(t, aws.StringValueSlice(cwdbNamesOlder), aws.StringValue(cwdbName)) +func (m mockedCloudWatchDashboard) ListDashboardsPages(input *cloudwatch.ListDashboardsInput, fn func(*cloudwatch.ListDashboardsOutput, bool) bool) error { + fn(&m.ListDashboardsOutput, true) + return nil } -func TestNukeCloudWatchDashboardOne(t *testing.T) { - telemetry.InitTelemetry("cloud-nuke", "") - t.Parallel() - - region, err := getRandomRegion() - require.NoError(t, err) - - session, err := session.NewSession(&aws.Config{Region: aws.String(region)}) - require.NoError(t, err) - svc := cloudwatch.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. - cwdbName := createCloudWatchDashboard(t, svc, region) - defer deleteCloudWatchDashboard(t, svc, cwdbName, false) - identifiers := []*string{cwdbName} - - require.NoError( - t, - nukeAllCloudWatchDashboards(session, identifiers), - ) - - // Make sure the CloudWatch Dashboard is deleted. - assertCloudWatchDashboardsDeleted(t, svc, identifiers) +func (m mockedCloudWatchDashboard) DeleteDashboards(input *cloudwatch.DeleteDashboardsInput) (*cloudwatch.DeleteDashboardsOutput, error) { + return &m.DeleteDashboardsOutput, nil } -func TestNukeCloudWatchDashboardsMoreThanOne(t *testing.T) { +func TestCloudWatchDashboard_GetAll(t *testing.T) { telemetry.InitTelemetry("cloud-nuke", "") t.Parallel() - region, err := getRandomRegion() - require.NoError(t, err) - - session, err := session.NewSession(&aws.Config{Region: aws.String(region)}) - require.NoError(t, err) - svc := cloudwatch.New(session) - - cwdbNames := []*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. - cwdbName := createCloudWatchDashboard(t, svc, region) - defer deleteCloudWatchDashboard(t, svc, cwdbName, false) - cwdbNames = append(cwdbNames, cwdbName) + testName1 := "test-name1" + testName2 := "test-name2" + now := time.Now() + cw := CloudWatchDashboards{ + Client: mockedCloudWatchDashboard{ + ListDashboardsOutput: cloudwatch.ListDashboardsOutput{ + DashboardEntries: []*cloudwatch.DashboardEntry{ + { + DashboardName: aws.String(testName1), + LastModified: &now, + }, + { + DashboardName: aws.String(testName2), + LastModified: aws.Time(now.Add(1)), + }, + }}, + }} + + tests := map[string]struct { + configObj config.ResourceType + expected []string + }{ + "emptyFilter": { + configObj: config.ResourceType{}, + expected: []string{testName1, testName2}, + }, + "nameExclusionFilter": { + configObj: config.ResourceType{ + ExcludeRule: config.FilterRule{ + NamesRegExp: []config.Expression{{ + RE: *regexp.MustCompile(testName1), + }}}, + }, + expected: []string{testName2}, + }, + "timeAfterExclusionFilter": { + configObj: config.ResourceType{ + ExcludeRule: config.FilterRule{ + TimeAfter: aws.Time(now.Add(-1)), + }}, + expected: []string{}, + }, } - require.NoError( - t, - nukeAllCloudWatchDashboards(session, cwdbNames), - ) - - // Make sure the CloudWatch Dashboard is deleted. - assertCloudWatchDashboardsDeleted(t, svc, cwdbNames) -} - -// Helper functions for driving the CloudWatch Dashboard tests - -// createCloudWatchDashboard will create a new CloudWatch Dashboard with a single text widget. -func createCloudWatchDashboard(t *testing.T, svc *cloudwatch.CloudWatch, region string) *string { - uniqueID := util.UniqueID() - name := fmt.Sprintf("cloud-nuke-test-%s", strings.ToLower(uniqueID)) + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + names, err := cw.getAll(config.Config{ + CloudWatchDashboard: tc.configObj, + }) - resp, err := svc.PutDashboard(&cloudwatch.PutDashboardInput{ - DashboardBody: aws.String(helloWorldCloudWatchDashboardWidget), - DashboardName: aws.String(name), - }) - require.NoError(t, err) - if len(resp.DashboardValidationMessages) > 0 { - t.Fatalf("Error creating Dashboard %v", resp.DashboardValidationMessages) + require.NoError(t, err) + require.Equal(t, tc.expected, aws.StringValueSlice(names)) + }) } - // Add an arbitrary sleep to account for eventual consistency - time.Sleep(15 * time.Second) - return &name } -// deleteCloudWatchDashboard is a function to delete the given CloudWatch Dashboard. -func deleteCloudWatchDashboard(t *testing.T, svc *cloudwatch.CloudWatch, name *string, checkErr bool) { - input := &cloudwatch.DeleteDashboardsInput{DashboardNames: []*string{name}} - _, err := svc.DeleteDashboards(input) - if checkErr { - require.NoError(t, err) - } -} +func TestCloudWatchDashboard_NukeAll(t *testing.T) { + telemetry.InitTelemetry("cloud-nuke", "") + t.Parallel() + cw := CloudWatchDashboards{ + Client: mockedCloudWatchDashboard{ + DeleteDashboardsOutput: cloudwatch.DeleteDashboardsOutput{}, + }} -func assertCloudWatchDashboardsDeleted(t *testing.T, svc *cloudwatch.CloudWatch, identifiers []*string) { - for _, name := range identifiers { - resp, err := svc.ListDashboards(&cloudwatch.ListDashboardsInput{DashboardNamePrefix: name}) - require.NoError(t, err) - if len(resp.DashboardEntries) > 0 { - t.Fatalf("Dashboard %s is not deleted", aws.StringValue(name)) - } - } + err := cw.nukeAll([]*string{aws.String("test-name1"), aws.String("test-name2")}) + require.NoError(t, err) } - -const helloWorldCloudWatchDashboardWidget = `{ - "widgets":[ - { - "type":"text", - "x":0, - "y":7, - "width":3, - "height":3, - "properties":{ - "markdown":"Hello world" - } - } - ] -}` diff --git a/aws/cloudwatch_dashboard_types.go b/aws/cloudwatch_dashboard_types.go index e7b8454f..2d0b003d 100644 --- a/aws/cloudwatch_dashboard_types.go +++ b/aws/cloudwatch_dashboard_types.go @@ -30,7 +30,7 @@ func (cwdb CloudWatchDashboards) MaxBatchSize() int { // Nuke - nuke 'em all!!! func (cwdb CloudWatchDashboards) Nuke(session *session.Session, identifiers []string) error { - if err := nukeAllCloudWatchDashboards(session, awsgo.StringSlice(identifiers)); err != nil { + if err := cwdb.nukeAll(awsgo.StringSlice(identifiers)); err != nil { return errors.WithStackTrace(err) } diff --git a/aws/cloudwatch_loggroup.go b/aws/cloudwatch_loggroup.go index 68ae704b..dbf15073 100644 --- a/aws/cloudwatch_loggroup.go +++ b/aws/cloudwatch_loggroup.go @@ -8,7 +8,6 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/cloudwatchlogs" "github.com/gruntwork-io/cloud-nuke/config" "github.com/gruntwork-io/cloud-nuke/logging" @@ -17,15 +16,22 @@ import ( "github.com/hashicorp/go-multierror" ) -func getAllCloudWatchLogGroups(session *session.Session, excludeAfter time.Time, configObj config.Config) ([]*string, error) { - svc := cloudwatchlogs.New(session) - +func (cwlg CloudWatchLogGroups) getAll(configObj config.Config) ([]*string, error) { allLogGroups := []*string{} - err := svc.DescribeLogGroupsPages( + err := cwlg.Client.DescribeLogGroupsPages( &cloudwatchlogs.DescribeLogGroupsInput{}, func(page *cloudwatchlogs.DescribeLogGroupsOutput, lastPage bool) bool { for _, logGroup := range page.LogGroups { - if shouldIncludeCloudWatchLogGroup(logGroup, excludeAfter, configObj) { + var creationTime *time.Time + if logGroup.CreationTime != nil { + // Convert milliseconds since epoch to time.Time object + creationTime = aws.Time(time.Unix(0, aws.Int64Value(logGroup.CreationTime)*int64(time.Millisecond))) + } + + if configObj.CloudWatchLogGroup.ShouldInclude(config.ResourceValue{ + Name: logGroup.LogGroupName, + Time: creationTime, + }) { allLogGroups = append(allLogGroups, logGroup.LogGroupName) } } @@ -38,32 +44,9 @@ func getAllCloudWatchLogGroups(session *session.Session, excludeAfter time.Time, return allLogGroups, nil } -func shouldIncludeCloudWatchLogGroup(logGroup *cloudwatchlogs.LogGroup, excludeAfter time.Time, configObj config.Config) bool { - if logGroup == nil { - return false - } - - if logGroup.CreationTime != nil { - // Convert milliseconds since epoch to time.Time object - creationTime := time.Unix(0, aws.Int64Value(logGroup.CreationTime)*int64(time.Millisecond)) - if excludeAfter.Before(creationTime) { - return false - } - } - - return config.ShouldInclude( - aws.StringValue(logGroup.LogGroupName), - configObj.CloudWatchLogGroup.IncludeRule.NamesRegExp, - configObj.CloudWatchLogGroup.ExcludeRule.NamesRegExp, - ) -} - -func nukeAllCloudWatchLogGroups(session *session.Session, identifiers []*string) error { - region := aws.StringValue(session.Config.Region) - svc := cloudwatchlogs.New(session) - +func (cwlg CloudWatchLogGroups) nukeAll(identifiers []*string) error { if len(identifiers) == 0 { - logging.Logger.Debugf("No CloudWatch Log Groups to nuke in region %s", *session.Config.Region) + logging.Logger.Debugf("No CloudWatch Log Groups to nuke in region %s", cwlg.Region) return nil } @@ -78,13 +61,13 @@ func nukeAllCloudWatchLogGroups(session *session.Session, identifiers []*string) // There is no bulk delete CloudWatch Log Group API, so we delete the batch of CloudWatch Log Groups concurrently // using go routines. - logging.Logger.Debugf("Deleting CloudWatch Log Groups in region %s", region) + logging.Logger.Debugf("Deleting CloudWatch Log Groups in region %s", cwlg.Region) wg := new(sync.WaitGroup) wg.Add(len(identifiers)) errChans := make([]chan error, len(identifiers)) for i, logGroupName := range identifiers { errChans[i] = make(chan error, 1) - go deleteCloudWatchLogGroupAsync(wg, errChans[i], svc, logGroupName, region) + go cwlg.deleteAsync(wg, errChans[i], logGroupName) } wg.Wait() @@ -99,7 +82,7 @@ func nukeAllCloudWatchLogGroups(session *session.Session, identifiers []*string) telemetry.TrackEvent(commonTelemetry.EventContext{ EventName: "Error Nuking Cloudwatch Log Group", }, map[string]interface{}{ - "region": *session.Config.Region, + "region": cwlg.Region, }) } } @@ -111,18 +94,12 @@ func nukeAllCloudWatchLogGroups(session *session.Session, identifiers []*string) return nil } -// deleteCloudWatchLogGroupAsync deletes the provided Log Group asynchronously in a goroutine, using wait groups for +// deleteAsync deletes the provided Log Group asynchronously in a goroutine, using wait groups for // concurrency control and a return channel for errors. -func deleteCloudWatchLogGroupAsync( - wg *sync.WaitGroup, - errChan chan error, - svc *cloudwatchlogs.CloudWatchLogs, - logGroupName *string, - region string, -) { +func (cwlg CloudWatchLogGroups) deleteAsync(wg *sync.WaitGroup, errChan chan error, logGroupName *string) { defer wg.Done() input := &cloudwatchlogs.DeleteLogGroupInput{LogGroupName: logGroupName} - _, err := svc.DeleteLogGroup(input) + _, err := cwlg.Client.DeleteLogGroup(input) // Record status of this resource e := report.Entry{ @@ -136,9 +113,9 @@ func deleteCloudWatchLogGroupAsync( logGroupNameStr := aws.StringValue(logGroupName) if err == nil { - logging.Logger.Debugf("[OK] CloudWatch Log Group %s deleted in %s", logGroupNameStr, region) + logging.Logger.Debugf("[OK] CloudWatch Log Group %s deleted in %s", logGroupNameStr, cwlg.Region) } else { - logging.Logger.Debugf("[Failed] Error deleting CloudWatch Log Group %s in %s: %s", logGroupNameStr, region, err) + logging.Logger.Debugf("[Failed] Error deleting CloudWatch Log Group %s in %s: %s", logGroupNameStr, cwlg.Region, err) } } diff --git a/aws/cloudwatch_loggroup_test.go b/aws/cloudwatch_loggroup_test.go index db4eca52..ab88f3d9 100644 --- a/aws/cloudwatch_loggroup_test.go +++ b/aws/cloudwatch_loggroup_test.go @@ -1,152 +1,101 @@ package aws import ( - "fmt" - "github.com/gruntwork-io/cloud-nuke/telemetry" - "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/aws/aws-sdk-go/service/cloudwatchlogs/cloudwatchlogsiface" "github.com/gruntwork-io/cloud-nuke/config" - "github.com/gruntwork-io/cloud-nuke/util" - "github.com/stretchr/testify/assert" + "github.com/gruntwork-io/cloud-nuke/telemetry" "github.com/stretchr/testify/require" + "regexp" + "testing" + "time" ) -func TestListCloudWatchLogGroups(t *testing.T) { - telemetry.InitTelemetry("cloud-nuke", "") - 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) - defer deleteCloudWatchLogGroup(t, svc, lgName, true) - - lgNames, err := getAllCloudWatchLogGroups(session, time.Now(), config.Config{}) - require.NoError(t, err) - assert.Contains(t, aws.StringValueSlice(lgNames), aws.StringValue(lgName)) +type mockedCloudWatchLogGroup struct { + cloudwatchlogsiface.CloudWatchLogsAPI + DeleteLogGroupOutput cloudwatchlogs.DeleteLogGroupOutput + DescribeLogGroupsOutput cloudwatchlogs.DescribeLogGroupsOutput } -func TestTimeFilterExclusionNewlyCreatedCloudWatchLogGroup(t *testing.T) { - telemetry.InitTelemetry("cloud-nuke", "") - 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) - defer deleteCloudWatchLogGroup(t, svc, lgName, true) - - // Assert CloudWatch Dashboard is picked up without filters - lgNames, err := getAllCloudWatchLogGroups(session, time.Now(), config.Config{}) - require.NoError(t, err) - assert.Contains(t, aws.StringValueSlice(lgNames), aws.StringValue(lgName)) - - // Assert user doesn't appear when we look at users older than 1 Hour - olderThan := time.Now().Add(-1 * time.Hour) - lgNamesOlder, err := getAllCloudWatchLogGroups(session, olderThan, config.Config{}) - require.NoError(t, err) - assert.NotContains(t, aws.StringValueSlice(lgNamesOlder), aws.StringValue(lgName)) +func (m mockedCloudWatchLogGroup) DescribeLogGroupsPages(input *cloudwatchlogs.DescribeLogGroupsInput, fn func(*cloudwatchlogs.DescribeLogGroupsOutput, bool) bool) error { + fn(&m.DescribeLogGroupsOutput, true) + return nil } -func TestNukeCloudWatchLogGroupOne(t *testing.T) { - telemetry.InitTelemetry("cloud-nuke", "") - 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) - 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 (m mockedCloudWatchLogGroup) DeleteLogGroup(input *cloudwatchlogs.DeleteLogGroupInput) (*cloudwatchlogs.DeleteLogGroupOutput, error) { + return &m.DeleteLogGroupOutput, nil } -func TestNukeCloudWatchLogGroupMoreThanOne(t *testing.T) { +func TestCloudWatchLogGroup_GetAll(t *testing.T) { telemetry.InitTelemetry("cloud-nuke", "") 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) - defer deleteCloudWatchLogGroup(t, svc, lgName, false) - lgNames = append(lgNames, lgName) + testName1 := "test-name1" + testName2 := "test-name2" + now := time.Now() + cw := CloudWatchLogGroups{ + Client: mockedCloudWatchLogGroup{ + DescribeLogGroupsOutput: cloudwatchlogs.DescribeLogGroupsOutput{ + LogGroups: []*cloudwatchlogs.LogGroup{ + { + LogGroupName: aws.String(testName1), + CreationTime: aws.Int64(now.UnixMilli()), + }, + { + LogGroupName: aws.String(testName2), + CreationTime: aws.Int64(now.Add(1).UnixMilli()), + }, + }, + }, + }} + + tests := map[string]struct { + configObj config.ResourceType + expected []string + }{ + "emptyFilter": { + configObj: config.ResourceType{}, + expected: []string{testName1, testName2}, + }, + "nameExclusionFilter": { + configObj: config.ResourceType{ + ExcludeRule: config.FilterRule{ + NamesRegExp: []config.Expression{{ + RE: *regexp.MustCompile(testName1), + }}}, + }, + expected: []string{testName2}, + }, + "timeAfterExclusionFilter": { + configObj: config.ResourceType{ + ExcludeRule: config.FilterRule{ + TimeAfter: aws.Time(now.Add(-2 * time.Hour)), + }}, + expected: []string{}, + }, } - require.NoError( - t, - nukeAllCloudWatchLogGroups(session, lgNames), - ) + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + names, err := cw.getAll(config.Config{ + CloudWatchLogGroup: tc.configObj, + }) - // Make sure the CloudWatch Dashboard is deleted. - assertCloudWatchLogGroupsDeleted(t, svc, lgNames) + require.NoError(t, err) + require.Equal(t, tc.expected, aws.StringValueSlice(names)) + }) + } } -func createCloudWatchLogGroup(t *testing.T, svc *cloudwatchlogs.CloudWatchLogs) *string { - uniqueID := util.UniqueID() - name := fmt.Sprintf("cloud-nuke-test-%s", strings.ToLower(uniqueID)) +func TestCloudWatchLogGroup_NukeAll(t *testing.T) { + telemetry.InitTelemetry("cloud-nuke", "") + t.Parallel() + cw := CloudWatchLogGroups{ + Client: mockedCloudWatchLogGroup{ + DeleteLogGroupOutput: cloudwatchlogs.DeleteLogGroupOutput{}, + }} - _, err := svc.CreateLogGroup(&cloudwatchlogs.CreateLogGroupInput{ - LogGroupName: aws.String(name), - }) + err := cw.nukeAll([]*string{aws.String("test-name1"), aws.String("test-name2")}) 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)) - } - } } diff --git a/aws/cloudwatch_loggroup_types.go b/aws/cloudwatch_loggroup_types.go index 26c8da3a..e09c8fe0 100644 --- a/aws/cloudwatch_loggroup_types.go +++ b/aws/cloudwatch_loggroup_types.go @@ -15,16 +15,16 @@ type CloudWatchLogGroups struct { } // ResourceName - the simple name of the aws resource -func (r CloudWatchLogGroups) ResourceName() string { +func (cwlg CloudWatchLogGroups) ResourceName() string { return "cloudwatch-loggroup" } // ResourceIdentifiers - The instance ids of the ec2 instances -func (r CloudWatchLogGroups) ResourceIdentifiers() []string { - return r.Names +func (cwlg CloudWatchLogGroups) ResourceIdentifiers() []string { + return cwlg.Names } -func (r CloudWatchLogGroups) MaxBatchSize() int { +func (cwlg CloudWatchLogGroups) MaxBatchSize() int { // Tentative batch size to ensure AWS doesn't throttle. Note that CloudWatch Logs does not support bulk delete, so // we will be deleting this many in parallel using go routines. We pick 35 here, which is half of what the AWS web // console will do. We pick a conservative number here to avoid hitting AWS API rate limits. @@ -32,8 +32,8 @@ func (r CloudWatchLogGroups) MaxBatchSize() int { } // Nuke - nuke 'em all!!! -func (r CloudWatchLogGroups) Nuke(session *session.Session, identifiers []string) error { - if err := nukeAllCloudWatchLogGroups(session, awsgo.StringSlice(identifiers)); err != nil { +func (cwlg CloudWatchLogGroups) Nuke(session *session.Session, identifiers []string) error { + if err := cwlg.nukeAll(awsgo.StringSlice(identifiers)); err != nil { return errors.WithStackTrace(err) }