diff --git a/aws/aws.go b/aws/aws.go index 444f3f82..66ff6346 100644 --- a/aws/aws.go +++ b/aws/aws.go @@ -1713,7 +1713,7 @@ func GetAllResources(targetRegions []string, excludeAfter time.Time, resourceTyp } if IsNukeable(snsTopics.ResourceName(), resourceTypes) { start := time.Now() - snsTopicArns, err := getAllSNSTopics(cloudNukeSession, excludeAfter, configObj) + snsTopicArns, err := snsTopics.getAll(configObj) if err != nil { ge := report.GeneralError{ Error: err, diff --git a/aws/sns.go b/aws/sns.go index 0ff50d61..5ebf811a 100644 --- a/aws/sns.go +++ b/aws/sns.go @@ -1,7 +1,7 @@ package aws import ( - "context" + "github.com/aws/aws-sdk-go/service/sns" "strings" "sync" "time" @@ -9,11 +9,7 @@ import ( "github.com/gruntwork-io/cloud-nuke/telemetry" commonTelemetry "github.com/gruntwork-io/go-commons/telemetry" - awsconfig "github.com/aws/aws-sdk-go-v2/config" - "github.com/aws/aws-sdk-go-v2/service/sns" - "github.com/aws/aws-sdk-go-v2/service/sns/types" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/session" "github.com/gruntwork-io/cloud-nuke/config" "github.com/gruntwork-io/cloud-nuke/logging" "github.com/gruntwork-io/cloud-nuke/report" @@ -24,52 +20,54 @@ import ( // getAllSNSTopics returns a list of all SNS topics in the region, filtering the name by the config // The SQS APIs do not return a creation date, therefore we tag the resources with a first seen time when the topic first appears. We then // use that tag to measure the excludeAfter time duration, and determine whether to nuke the resource based on that. -func getAllSNSTopics(session *session.Session, excludeAfter time.Time, configObj config.Config) ([]*string, error) { - ctx := context.TODO() +func (s SNSTopic) getAll(configObj config.Config) ([]*string, error) { - cfg, err := awsconfig.LoadDefaultConfig(ctx, awsconfig.WithRegion(aws.StringValue(session.Config.Region))) - if err != nil { - return []*string{}, errors.WithStackTrace(err) - } - svc := sns.NewFromConfig(cfg) - - snsTopics := []*string{} - - paginator := sns.NewListTopicsPaginator(svc, nil) - - for paginator.HasMorePages() { - resp, err := paginator.NextPage(ctx) - if err != nil { - return []*string{}, errors.WithStackTrace(err) - } - for _, topic := range resp.Topics { - firstSeenTime, err := getFirstSeenSNSTopicTag(ctx, svc, *topic.TopicArn, firstSeenTagKey) + var snsTopics []*string + err := s.Client.ListTopicsPages(&sns.ListTopicsInput{}, func(page *sns.ListTopicsOutput, lastPage bool) bool { + for _, topic := range page.Topics { + firstSeenTime, err := s.getFirstSeenSNSTopicTag(*topic.TopicArn) if err != nil { - logging.Logger.Errorf("Unable to retrieve tags for SNS Topic: %s, with error: %s", *topic.TopicArn, err) - return nil, err + logging.Logger.Errorf( + "Unable to retrieve tags for SNS Topic: %s, with error: %s", *topic.TopicArn, err) + continue } if firstSeenTime == nil { now := time.Now().UTC() firstSeenTime = &now - if err := setFirstSeenSNSTopicTag(ctx, svc, *topic.TopicArn, firstSeenTagKey, now); err != nil { - logging.Logger.Errorf("Unable to apply first seen tag SNS Topic: %s, with error: %s", *topic.TopicArn, err) - return nil, err + if err := s.setFirstSeenSNSTopicTag(*topic.TopicArn, now); err != nil { + logging.Logger.Errorf( + "Unable to apply first seen tag SNS Topic: %s, with error: %s", *topic.TopicArn, err) + continue } } - if shouldIncludeSNS(*topic.TopicArn, excludeAfter, *firstSeenTime, configObj) { + // a topic arn is of the form arn:aws:sns:us-east-1:123456789012:MyTopic + // so we can search for the index of the last colon, then slice the string to get the topic name + nameIndex := strings.LastIndex(*topic.TopicArn, ":") + topicName := (*topic.TopicArn)[nameIndex+1:] + if configObj.SNS.ShouldInclude(config.ResourceValue{ + Time: firstSeenTime, + Name: &topicName, + }) { snsTopics = append(snsTopics, topic.TopicArn) } } + + return !lastPage + }) + + if err != nil { + return nil, errors.WithStackTrace(err) } + return snsTopics, nil } // getFirstSeenSNSTopicTag will retrive the time that the topic was first seen, otherwise returning nil if the topic has not been // seen before. -func getFirstSeenSNSTopicTag(ctx context.Context, svc *sns.Client, topicArn, key string) (*time.Time, error) { - response, err := svc.ListTagsForResource(ctx, &sns.ListTagsForResourceInput{ +func (s SNSTopic) getFirstSeenSNSTopicTag(topicArn string) (*time.Time, error) { + response, err := s.Client.ListTagsForResource(&sns.ListTagsForResourceInput{ ResourceArn: &topicArn, }) if err != nil { @@ -77,7 +75,7 @@ func getFirstSeenSNSTopicTag(ctx context.Context, svc *sns.Client, topicArn, key } for i := range response.Tags { - if *response.Tags[i].Key == key { + if *response.Tags[i].Key == firstSeenTagKey { firstSeenTime, err := time.Parse(firstSeenTimeFormat, *response.Tags[i].Value) if err != nil { return nil, err @@ -91,16 +89,15 @@ func getFirstSeenSNSTopicTag(ctx context.Context, svc *sns.Client, topicArn, key } // setFirstSeenSNSTopic will append a tag to the SNS Topic that details the first seen time. -func setFirstSeenSNSTopicTag(ctx context.Context, svc *sns.Client, topicArn, key string, value time.Time) error { +func (s SNSTopic) setFirstSeenSNSTopicTag(topicArn string, value time.Time) error { timeValue := value.Format(firstSeenTimeFormat) - _, err := svc.TagResource( - ctx, + _, err := s.Client.TagResource( &sns.TagResourceInput{ ResourceArn: &topicArn, - Tags: []types.Tag{ + Tags: []*sns.Tag{ { - Key: &key, + Key: aws.String(firstSeenTagKey), Value: &timeValue, }, }, @@ -127,17 +124,9 @@ func shouldIncludeSNS(topicArn string, excludeAfter, firstSeenTime time.Time, co return config.ShouldInclude(topicName, configObj.SNS.IncludeRule.NamesRegExp, configObj.SNS.ExcludeRule.NamesRegExp) } -func nukeAllSNSTopics(session *session.Session, identifiers []*string) error { - region := aws.StringValue(session.Config.Region) - - cfg, err := awsconfig.LoadDefaultConfig(context.TODO(), awsconfig.WithRegion(aws.StringValue(session.Config.Region))) - if err != nil { - return errors.WithStackTrace(err) - } - svc := sns.NewFromConfig(cfg) - +func (s SNSTopic) nukeAll(identifiers []*string) error { if len(identifiers) == 0 { - logging.Logger.Debugf("No SNS Topics to nuke in region %s", region) + logging.Logger.Debugf("No SNS Topics to nuke in region %s", s.Region) } if len(identifiers) > 100 { @@ -146,13 +135,13 @@ func nukeAllSNSTopics(session *session.Session, identifiers []*string) error { } // There is no bulk delete SNS API, so we delete the batch of SNS Topics concurrently using goroutines - logging.Logger.Debugf("Deleting SNS Topics in region %s", region) + logging.Logger.Debugf("Deleting SNS Topics in region %s", s.Region) wg := new(sync.WaitGroup) wg.Add(len(identifiers)) errChans := make([]chan error, len(identifiers)) for i, topicArn := range identifiers { errChans[i] = make(chan error, 1) - go deleteSNSTopicAsync(wg, errChans[i], svc, topicArn, region) + go s.deleteAsync(wg, errChans[i], topicArn) } wg.Wait() @@ -164,7 +153,7 @@ func nukeAllSNSTopics(session *session.Session, identifiers []*string) error { telemetry.TrackEvent(commonTelemetry.EventContext{ EventName: "Error Nuking SNS Topic", }, map[string]interface{}{ - "region": *session.Config.Region, + "region": s.Region, }) } } @@ -175,16 +164,16 @@ func nukeAllSNSTopics(session *session.Session, identifiers []*string) error { return nil } -func deleteSNSTopicAsync(wg *sync.WaitGroup, errChan chan error, svc *sns.Client, topicArn *string, region string) { +func (s SNSTopic) deleteAsync(wg *sync.WaitGroup, errChan chan error, topicArn *string) { defer wg.Done() deleteParam := &sns.DeleteTopicInput{ TopicArn: topicArn, } - logging.Logger.Debugf("Deleting SNS Topic (arn=%s) in region: %s", aws.StringValue(topicArn), region) + logging.Logger.Debugf("Deleting SNS Topic (arn=%s) in region: %s", aws.StringValue(topicArn), s.Region) - _, err := svc.DeleteTopic(context.TODO(), deleteParam) + _, err := s.Client.DeleteTopic(deleteParam) errChan <- err @@ -197,8 +186,8 @@ func deleteSNSTopicAsync(wg *sync.WaitGroup, errChan chan error, svc *sns.Client report.Record(e) if err == nil { - logging.Logger.Debugf("[OK] Deleted SNS Topic (arn=%s) in region: %s", aws.StringValue(topicArn), region) + logging.Logger.Debugf("[OK] Deleted SNS Topic (arn=%s) in region: %s", aws.StringValue(topicArn), s.Region) } else { - logging.Logger.Debugf("[Failed] Error deleting SNS Topic (arn=%s) in %s", aws.StringValue(topicArn), region) + logging.Logger.Debugf("[Failed] Error deleting SNS Topic (arn=%s) in %s", aws.StringValue(topicArn), s.Region) } } diff --git a/aws/sns_test.go b/aws/sns_test.go index f323bdaa..b2a10d95 100644 --- a/aws/sns_test.go +++ b/aws/sns_test.go @@ -1,347 +1,121 @@ package aws import ( - "context" - "fmt" - "math/rand" + "github.com/aws/aws-sdk-go/service/sns/snsiface" "regexp" "testing" "time" "github.com/gruntwork-io/cloud-nuke/telemetry" - awsconfig "github.com/aws/aws-sdk-go-v2/config" - "github.com/aws/aws-sdk-go-v2/service/sns" "github.com/aws/aws-sdk-go/aws" awsgo "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/sns" "github.com/gruntwork-io/cloud-nuke/config" - "github.com/gruntwork-io/cloud-nuke/util" - "github.com/gruntwork-io/go-commons/errors" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -type TestSNSTopic struct { - Name *string - Arn *string +type mockedSNSTopic struct { + snsiface.SNSAPI + ListTopicsOutput sns.ListTopicsOutput + ListTagsForResourceOutputMap map[string]sns.ListTagsForResourceOutput + DeleteTopicOutput sns.DeleteTopicOutput } -func createTestSNSTopic(t *testing.T, session *session.Session, name string, setFirstSeenTag bool) (*TestSNSTopic, error) { - cfg, err := awsconfig.LoadDefaultConfig(context.TODO(), awsconfig.WithRegion(aws.StringValue(session.Config.Region))) - require.NoError(t, err) - - svc := sns.NewFromConfig(cfg) - - param := &sns.CreateTopicInput{ - Name: &name, - } - - // Do a coin-flip to choose either a FIFO or Standard SNS Topic - coin := []string{ - "true", - "false", - } - rand.Seed(time.Now().UnixNano()) - coinflip := coin[rand.Intn(len(coin))] - param.Attributes = make(map[string]string) - param.Attributes["FifoTopic"] = coinflip - - // If we did choose to create a fifo queue, the name must end in ".fifo" - if coinflip == "true" { - param.Name = aws.String(fmt.Sprintf("%s.fifo", aws.StringValue(param.Name))) - } - - output, err := svc.CreateTopic(context.TODO(), param) - if err != nil { - assert.Failf(t, "Could not create test SNS Topic: %s", errors.WithStackTrace(err).Error()) - } - - if setFirstSeenTag { - // Set the first seen tag on the SNS Topic - err := setFirstSeenSNSTopicTag(context.TODO(), svc, *output.TopicArn, firstSeenTagKey, time.Now()) - if err != nil { - return nil, err - } - } - - return &TestSNSTopic{ - Name: param.Name, - Arn: output.TopicArn, - }, nil +func (m mockedSNSTopic) ListTopicsPages(input *sns.ListTopicsInput, fn func(*sns.ListTopicsOutput, bool) bool) error { + fn(&m.ListTopicsOutput, true) + return nil } -func TestListSNSTopics(t *testing.T) { - telemetry.InitTelemetry("cloud-nuke", "") - t.Parallel() +func (m mockedSNSTopic) ListTagsForResource(input *sns.ListTagsForResourceInput) (*sns.ListTagsForResourceOutput, error) { + arn := input.ResourceArn + resp := m.ListTagsForResourceOutputMap[*arn] - region, err := getRandomRegion() - require.NoError(t, err) - session, err := session.NewSession(&awsgo.Config{ - Region: awsgo.String(region), - }, - ) - if err != nil { - assert.Fail(t, errors.WithStackTrace(err).Error()) - } - - snsTopicName := "aws-nuke-test-" + util.UniqueID() - testSNSTopic, createTestSNSTopicErr := createTestSNSTopic(t, session, snsTopicName, true) - require.NoError(t, createTestSNSTopicErr) - // clean up after this test - defer nukeAllSNSTopics(session, []*string{testSNSTopic.Arn}) - - snsTopicArns, err := getAllSNSTopics(session, time.Now(), config.Config{}) - if err != nil { - assert.Fail(t, "Unable to fetch list of SNS Topics") - } - - assert.Contains(t, awsgo.StringValueSlice(snsTopicArns), aws.StringValue(testSNSTopic.Arn)) + return &resp, nil } -func TestNukeSNSTopicOne(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) - - snsTopicName := "aws-nuke-test-" + util.UniqueID() - - testSNSTopic, createTestSNSTopicErr := createTestSNSTopic(t, session, snsTopicName, true) - require.NoError(t, createTestSNSTopicErr) - - nukeErr := nukeAllSNSTopics(session, []*string{testSNSTopic.Arn}) - require.NoError(t, nukeErr) - - // Make sure the SNS Topic was deleted - snsTopicArns, err := getAllSNSTopics(session, time.Now(), config.Config{}) - require.NoError(t, err) - - assert.NotContains(t, aws.StringValueSlice(snsTopicArns), aws.StringValue(testSNSTopic.Arn)) +func (m mockedSNSTopic) DeleteTopic(input *sns.DeleteTopicInput) (*sns.DeleteTopicOutput, error) { + return &m.DeleteTopicOutput, nil } -func TestNukeSNSTopicMoreThanOne(t *testing.T) { +func TestSNS_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) - - testSNSTopicName := "aws-nuke-test-" + util.UniqueID() - testSNSTopicName2 := "aws-nuke-test-" + util.UniqueID() - - testSNSTopic, createTestErr := createTestSNSTopic(t, session, testSNSTopicName, true) - require.NoError(t, createTestErr) - testSNSTopic2, createTestErr2 := createTestSNSTopic(t, session, testSNSTopicName2, true) - require.NoError(t, createTestErr2) - - nukeErr := nukeAllSNSTopics(session, []*string{testSNSTopic.Arn, testSNSTopic2.Arn}) - require.NoError(t, nukeErr) - - // Make sure the SNS topics were deleted - snsTopicArns, err := getAllSNSTopics(session, time.Now(), config.Config{}) - require.NoError(t, err) - - assert.NotContains(t, aws.StringValueSlice(snsTopicArns), aws.StringValue(testSNSTopic.Arn)) - assert.NotContains(t, aws.StringValueSlice(snsTopicArns), aws.StringValue(testSNSTopic2.Arn)) -} - -func TestNukeSNSTopicWithFilter(t *testing.T) { - region, err := getRandomRegion() - require.NoError(t, err) - - session, err := session.NewSession(&aws.Config{Region: aws.String(region)}) - require.NoError(t, err) - - testSNSTopicName := "aws-nuke-test-" + util.UniqueID() - testSNSTopicName2 := "aws-do-not-nuke-test-" + util.UniqueID() - - testSNSTopic, createTestErr := createTestSNSTopic(t, session, testSNSTopicName, true) - require.NoError(t, createTestErr) - - testSNSTopic2, createTestErr2 := createTestSNSTopic(t, session, testSNSTopicName2, true) - require.NoError(t, createTestErr2) - - // as sns topics are online, lets clean up after this test - defer nukeAllSNSTopics(session, []*string{testSNSTopic.Arn, testSNSTopic2.Arn}) - - topics, err := getAllSNSTopics(session, time.Now(), config.Config{ - SNS: config.ResourceType{ - ExcludeRule: config.FilterRule{ - NamesRegExp: []config.Expression{{RE: *regexp.MustCompile("aws-do-not-nuke-test-.*")}}, + testTopic1 := "arn:aws:sns:us-east-1:123456789012:MyTopic1" + testTopic2 := "arn:aws:sns:us-east-1:123456789012:MyTopic2" + now := time.Now() + s := SNSTopic{ + Client: mockedSNSTopic{ + ListTopicsOutput: sns.ListTopicsOutput{ + Topics: []*sns.Topic{ + {TopicArn: awsgo.String(testTopic1)}, + {TopicArn: awsgo.String(testTopic2)}, + }, + }, + ListTagsForResourceOutputMap: map[string]sns.ListTagsForResourceOutput{ + testTopic1: { + Tags: []*sns.Tag{{ + Key: awsgo.String(firstSeenTagKey), + Value: awsgo.String(now.Format(firstSeenTimeFormat)), + }}, + }, + testTopic2: { + Tags: []*sns.Tag{{ + Key: awsgo.String(firstSeenTagKey), + Value: awsgo.String(now.Add(1).Format(firstSeenTimeFormat)), + }}, + }, }, }, - }) - require.NoError(t, err) - - // with the filters, we expect to only see the testSNSTopic in the findings, and not the testSNSTopic2 - assert.NotContains(t, aws.StringValueSlice(topics), aws.StringValue(testSNSTopic2.Arn)) - assert.Contains(t, aws.StringValueSlice(topics), aws.StringValue(testSNSTopic.Arn)) -} - -func TestSNSFirstSeenTagLogicIsCorrect(t *testing.T) { - ctx := context.Background() - - region, err := getRandomRegion() - require.NoError(t, err) - - session, err := session.NewSession(&aws.Config{Region: aws.String(region)}) - require.NoError(t, err) - - snsTopicName := "aws-nuke-test-" + util.UniqueID() - testSNSTopic, createTestSNSTopicErr := createTestSNSTopic(t, session, snsTopicName, false) - require.NoError(t, createTestSNSTopicErr) - - // clean up after this test - defer nukeAllSNSTopics(session, []*string{testSNSTopic.Arn}) - - cfg, err := awsconfig.LoadDefaultConfig(ctx, awsconfig.WithRegion(aws.StringValue(session.Config.Region))) - require.NoError(t, err) - - svc := sns.NewFromConfig(cfg) - - // check that the first seen tag is not present - firstSeen, err := getFirstSeenSNSTopicTag(ctx, svc, *testSNSTopic.Arn, firstSeenTagKey) - require.NoError(t, err) - assert.Nil(t, firstSeen) - - // update the first seen tag - now := time.Now().UTC() - err = setFirstSeenSNSTopicTag(ctx, svc, *testSNSTopic.Arn, firstSeenTagKey, now) - require.NoError(t, err) - - // check that the first seen tag was updated - firstSeen, err = getFirstSeenSNSTopicTag(ctx, svc, *testSNSTopic.Arn, firstSeenTagKey) - require.NoError(t, err) - - // We lose some precision when we tag the resource with the time due to the format, so to compare like for like, - // cast both to the same string format, which is also the same format used by the firstSeenSNSTopicTag function - assert.Equal(t, now.Format(firstSeenTimeFormat), firstSeen.Format(firstSeenTimeFormat)) -} - -func TestNukeSNSTopicWithTimeExclusion(t *testing.T) { - ctx := context.Background() - - region, err := getRandomRegion() - require.NoError(t, err) - - session, err := session.NewSession(&aws.Config{Region: aws.String(region)}) - require.NoError(t, err) - - snsTopicName := "aws-nuke-test-" + util.UniqueID() - testSNSTopic, createTestSNSTopicErr := createTestSNSTopic(t, session, snsTopicName, true) - require.NoError(t, createTestSNSTopicErr) - - // clean up after this test - defer nukeAllSNSTopics(session, []*string{testSNSTopic.Arn}) - - cfg, err := awsconfig.LoadDefaultConfig(ctx, awsconfig.WithRegion(aws.StringValue(session.Config.Region))) - require.NoError(t, err) - svc := sns.NewFromConfig(cfg) - - // update the tag on the sns topic to be 1 in the future - oneHourAgo := time.Now().UTC().Add(-1 * time.Hour) - err = setFirstSeenSNSTopicTag(ctx, svc, *testSNSTopic.Arn, firstSeenTagKey, oneHourAgo) - require.NoError(t, err) - - // ensure the sns topic is not found when we search for sns topics created, with a time exclusion - // that is 2 hours ago - twoHoursAgo := oneHourAgo.Add(-1 * time.Hour) - topics, err := getAllSNSTopics(session, twoHoursAgo, config.Config{}) - require.NoError(t, err) - - assert.NotContains(t, aws.StringValueSlice(topics), aws.StringValue(testSNSTopic.Arn)) -} - -func TestShouldIncludeSNS(t *testing.T) { - topic_name := func(name string) string { - return "arn:aws:sns:us-east-1:123456789012:" + name } tests := map[string]struct { - excludeAfter time.Time - firstSeenTime time.Time - config config.Config - TopicArn string - Expected bool + configObj config.ResourceType + expected []string }{ - "should include sns topic when no config is provided": { - TopicArn: topic_name("test-topic"), - Expected: true, + "emptyFilter": { + configObj: config.ResourceType{}, + expected: []string{testTopic1, testTopic2}, }, - "should not include sns topic when name matches excludes": { - TopicArn: topic_name("test-topic"), - config: config.Config{ - SNS: config.ResourceType{ - ExcludeRule: config.FilterRule{ - NamesRegExp: []config.Expression{{RE: *regexp.MustCompile("test-topic")}}, - }, - }, - }, - Expected: false, - }, - "should include sns topic when name matches includes": { - TopicArn: topic_name("test-topic"), - config: config.Config{ - SNS: config.ResourceType{ - IncludeRule: config.FilterRule{ - NamesRegExp: []config.Expression{{RE: *regexp.MustCompile("test-topic")}}, - }, - }, - }, - Expected: true, - }, - "should not include sns topic when name matches excludes and includes": { - TopicArn: topic_name("test-topic"), - config: config.Config{ - SNS: config.ResourceType{ - ExcludeRule: config.FilterRule{ - NamesRegExp: []config.Expression{{RE: *regexp.MustCompile("test-topic")}}, - }, - IncludeRule: config.FilterRule{ - NamesRegExp: []config.Expression{{RE: *regexp.MustCompile("test-topic")}}, - }, - }, + "nameExclusionFilter": { + configObj: config.ResourceType{ + ExcludeRule: config.FilterRule{ + NamesRegExp: []config.Expression{{ + RE: *regexp.MustCompile("MyTopic1"), + }}}, }, - Expected: false, + expected: []string{testTopic2}, }, - "should not include sns topic when excludes after is before first seen time": { - TopicArn: topic_name("test-topic"), - excludeAfter: time.Now().UTC().Add(-1 * time.Hour), - firstSeenTime: time.Now().UTC(), - Expected: false, - }, - "should include sns topic when excludes after is after first seen time": { - TopicArn: topic_name("test-topic"), - excludeAfter: time.Now().UTC().Add(1 * time.Hour), - firstSeenTime: time.Now().UTC(), - Expected: true, - }, - "should not include sns topic when excludes after is before first seen, but config includes the name": { - TopicArn: topic_name("test-topic"), - excludeAfter: time.Now().UTC().Add(-1 * time.Hour), - firstSeenTime: time.Now().UTC(), - config: config.Config{ - SNS: config.ResourceType{ - IncludeRule: config.FilterRule{ - NamesRegExp: []config.Expression{{RE: *regexp.MustCompile("test-topic")}}, - }, - }, - }, - Expected: false, + "timeAfterExclusionFilter": { + configObj: config.ResourceType{ + ExcludeRule: config.FilterRule{ + TimeAfter: aws.Time(now.Add(-1 * time.Hour)), + }}, + expected: []string{}, }, } - - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { - shouldInclude := shouldIncludeSNS(test.TopicArn, test.excludeAfter, test.firstSeenTime, test.config) - assert.Equal(t, test.Expected, shouldInclude) + names, err := s.getAll(config.Config{ + SNS: tc.configObj, + }) + require.NoError(t, err) + require.Equal(t, tc.expected, aws.StringValueSlice(names)) }) } } + +func TestSNS_NukeAll(t *testing.T) { + telemetry.InitTelemetry("cloud-nuke", "") + t.Parallel() + + s := SNSTopic{ + Client: mockedSNSTopic{ + DeleteTopicOutput: sns.DeleteTopicOutput{}, + }, + } + + err := s.nukeAll([]*string{aws.String("test")}) + require.NoError(t, err) +} diff --git a/aws/sns_types.go b/aws/sns_types.go index f4535021..016207be 100644 --- a/aws/sns_types.go +++ b/aws/sns_types.go @@ -26,7 +26,7 @@ func (s SNSTopic) MaxBatchSize() int { } func (s SNSTopic) Nuke(session *session.Session, identifiers []string) error { - if err := nukeAllSNSTopics(session, awsgo.StringSlice(identifiers)); err != nil { + if err := s.nukeAll(awsgo.StringSlice(identifiers)); err != nil { return errors.WithStackTrace(err) } return nil