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

add support for eventBridge schedule & schedule groups. #768

Merged
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ Cloud-nuke suppports 🔎 inspecting and 🔥💀 deleting the following AWS res
| Event Bridge | Event buses |
| Event Bridge | Archive |
| Event Bridge | Rule |
| Event Bridge | Schedule |
| Event Bridge | Schedule Group |
| Certificate Manager | ACM Private CA |
| Direct Connect | Transit Gateways |
| Elasticache | Clusters |
Expand Down Expand Up @@ -599,6 +601,8 @@ of the file that are supported are listed here.
| event-bridge | EventBridge | ✅ (Bus Name) | ✅ (Creation Time) | ❌ | ✅ |
| event-bridge-archive | EventBridgeArchive | ✅ (Archive Name) | ✅ (Creation Time) | ❌ | ✅ |
| event-bridge-rule | EventBridgeRule | ✅ (Bus Rule Name) | ❌ | ❌ | ✅ |
| event-bridge-schedule | EventBridgeSchedule | ✅ (Schedule Name) | ✅ (Creation Time) | ❌ | ✅ |
| event-bridge-schedule-group | EventBridgeScheduleGroup | ✅ (Schedule Group Name) | ✅ (Creation Time) | ❌ | ✅ |
| guardduty | GuardDuty | ❌ | ✅ (Created Time) | ❌ | ✅ |
| iam-group | IAMGroups | ✅ (Group Name) | ✅ (Creation Time) | ❌ | ✅ |
| iam-policy | IAMPolicies | ✅ (Policy Name) | ✅ (Creation Time) | ❌ | ✅ |
Expand Down
2 changes: 2 additions & 0 deletions aws/resource_registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ func getRegisteredRegionalResources() []AwsResource {
&resources.AppRunnerService{},
&resources.BackupVault{},
&resources.ManagedPrometheus{},
&resources.EventBridgeSchedule{},
&resources.EventBridgeScheduleGroup{},
&resources.EventBridgeArchive{},
&resources.EventBridgeRule{},
&resources.EventBridge{},
Expand Down
84 changes: 84 additions & 0 deletions aws/resources/eventbridge_schedule.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package resources

import (
"context"
"fmt"
"strings"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/scheduler"
"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/go-commons/errors"
)

func (sch *EventBridgeSchedule) nukeAll(identifiers []*string) error {
if len(identifiers) == 0 {
logging.Debugf("[Event Bridge Schedule] No Schedules found in region %s", sch.Region)
return nil
}

logging.Debugf("[Event Bridge Schedule] Deleting all Schedules in %s", sch.Region)

var deleted []*string
for _, identifier := range identifiers {
payload := strings.Split(*identifier, "|")
if len(payload) != 2 {
logging.Debugf("[Event Bridge Schedule] Invalid identifier %s", *identifier)
continue
}

_, err := sch.Client.DeleteSchedule(sch.Context, &scheduler.DeleteScheduleInput{
GroupName: aws.String(payload[0]),
Name: aws.String(payload[1]),
})

if err != nil {
logging.Debugf(
"[Event Bridge Schedule] Error deleting Schedule %s in region %s, err %s",
*identifier,
sch.Region,
err,
)
} else {
deleted = append(deleted, identifier)
logging.Debugf("[Event Bridge Schedule] Deleted Schedule %s in region %s", *identifier, sch.Region)
}

e := report.Entry{
Identifier: aws.ToString(identifier),
ResourceType: sch.ResourceName(),
Error: err,
}
report.Record(e)
}

logging.Debugf("[OK] %d Event Bridges Schedul(es) deleted in %s", len(deleted), sch.Region)
return nil
}

func (sch *EventBridgeSchedule) getAll(ctx context.Context, cnfObj config.Config) ([]*string, error) {
var identifiers []*string

paginator := scheduler.NewListSchedulesPaginator(sch.Client, &scheduler.ListSchedulesInput{})
for paginator.HasMorePages() {
page, err := paginator.NextPage(ctx)
if err != nil {
logging.Debugf("[Event Bridge Schedule] Failed to list schedules: %s", err)
return nil, errors.WithStackTrace(err)
}

for _, schedule := range page.Schedules {
id := aws.String(fmt.Sprintf("%s|%s", *schedule.GroupName, *schedule.Name))
if cnfObj.EventBridgeSchedule.ShouldInclude(config.ResourceValue{
Name: id,
Time: schedule.CreationDate,
}) {
identifiers = append(identifiers, id)
}
}
}

return identifiers, nil
}
85 changes: 85 additions & 0 deletions aws/resources/eventbridge_schedule_group.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package resources

import (
"context"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/scheduler"
"github.com/aws/aws-sdk-go-v2/service/scheduler/types"
"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/go-commons/errors"
)

func (sch *EventBridgeScheduleGroup) nukeAll(identifiers []*string) error {
if len(identifiers) == 0 {
logging.Debugf("[Event Bridge Schedule] No Groups found in region %s", sch.Region)
return nil
}

logging.Debugf("[Event Bridge Schedule] Deleting all Groups in %s", sch.Region)

var deleted []*string
for _, identifier := range identifiers {
_, err := sch.Client.DeleteScheduleGroup(sch.Context, &scheduler.DeleteScheduleGroupInput{
Name: identifier,
})

if err != nil {
logging.Debugf(
"[Event Bridge Schedule] Error deleting Group %s in region %s, err %s",
*identifier,
sch.Region,
err,
)
} else {
deleted = append(deleted, identifier)
logging.Debugf("[Event Bridge Schedule] Deleted Group %s in region %s", *identifier, sch.Region)
}

e := report.Entry{
Identifier: aws.ToString(identifier),
ResourceType: sch.ResourceName(),
Error: err,
}
report.Record(e)
}

logging.Debugf("[OK] %d Event Bridges Schedul Groups deleted in %s", len(deleted), sch.Region)
return nil
}

func (sch *EventBridgeScheduleGroup) getAll(ctx context.Context, cnfObj config.Config) ([]*string, error) {
var identifiers []*string
paginator := scheduler.NewListScheduleGroupsPaginator(sch.Client, &scheduler.ListScheduleGroupsInput{})

for paginator.HasMorePages() {
page, err := paginator.NextPage(ctx)
if err != nil {
logging.Debugf("[Event Bridge Schedule] Failed to list schedule groups: %s", err)
return nil, errors.WithStackTrace(err)
}

for _, group := range page.ScheduleGroups {
if cnfObj.EventBridgeScheduleGroup.ShouldInclude(config.ResourceValue{
Name: group.Name,
Time: group.CreationDate,
}) {
if *group.Name == "default" {
logging.Debug("[Event Bridge Schedule] skipping default group")
continue
}

if group.State != types.ScheduleGroupStateActive {
logging.Debugf("[Event Bridge Schedule] skipping group %s, wrong state %s", *group.Name, group.State)
continue
}

identifiers = append(identifiers, group.Name)
}
}
}

return identifiers, nil
}
104 changes: 104 additions & 0 deletions aws/resources/eventbridge_schedule_group_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package resources

import (
"context"
"regexp"
"testing"
"time"

"github.com/aws/aws-sdk-go-v2/service/scheduler"
"github.com/aws/aws-sdk-go-v2/service/scheduler/types"
"github.com/aws/aws-sdk-go/aws"
"github.com/gruntwork-io/cloud-nuke/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

type mockedEventBridgeScheduleGroupService struct {
EventBridgeScheduleGroupAPI
ListScheduleGroupsOutput scheduler.ListScheduleGroupsOutput
DeleteScheduleGroupOutput scheduler.DeleteScheduleGroupOutput
}

func (m mockedEventBridgeScheduleGroupService) DeleteScheduleGroup(ctx context.Context, params *scheduler.DeleteScheduleGroupInput, optFns ...func(*scheduler.Options)) (*scheduler.DeleteScheduleGroupOutput, error) {
return &m.DeleteScheduleGroupOutput, nil
}

func (m mockedEventBridgeScheduleGroupService) ListScheduleGroups(ctx context.Context, params *scheduler.ListScheduleGroupsInput, optFns ...func(*scheduler.Options)) (*scheduler.ListScheduleGroupsOutput, error) {
return &m.ListScheduleGroupsOutput, nil
}

func Test_EventBridgeScheduleGroup_GetAll(t *testing.T) {
t.Parallel()

now := time.Now()

group1 := "test-group-1"
group2 := "test-group-2"

service := EventBridgeScheduleGroup{Client: mockedEventBridgeScheduleGroupService{
ListScheduleGroupsOutput: scheduler.ListScheduleGroupsOutput{
ScheduleGroups: []types.ScheduleGroupSummary{
{
Name: aws.String(group1),
State: types.ScheduleGroupStateActive,
CreationDate: &now,
},
{
Name: aws.String(group2),
State: types.ScheduleGroupStateActive,
CreationDate: aws.Time(now.Add(time.Hour)),
},
},
},
}}

tests := map[string]struct {
configObj config.ResourceType
expected []string
}{
"emptyFilter": {
configObj: config.ResourceType{},
expected: []string{group1, group2},
},
"nameExclusionFilter": {
configObj: config.ResourceType{
ExcludeRule: config.FilterRule{
NamesRegExp: []config.Expression{{
RE: *regexp.MustCompile(group1),
}},
}},
expected: []string{group2},
},
"timeAfterExclusionFilter": {
configObj: config.ResourceType{
ExcludeRule: config.FilterRule{
TimeAfter: aws.Time(now.Add(-1 * time.Hour)),
}},
expected: []string{},
},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
buses, err := service.getAll(
context.Background(),
config.Config{EventBridgeScheduleGroup: tc.configObj},
)
require.NoError(t, err)
require.Equal(t, tc.expected, aws.StringValueSlice(buses))
})
}
}

func Test_EventBridgeScheduleGroup_NukeAll(t *testing.T) {
t.Parallel()

groupName := "test-group"
service := EventBridgeScheduleGroup{Client: mockedEventBridgeScheduleGroupService{
DeleteScheduleGroupOutput: scheduler.DeleteScheduleGroupOutput{},
}}

err := service.nukeAll([]*string{&groupName})
assert.NoError(t, err)
}
58 changes: 58 additions & 0 deletions aws/resources/eventbridge_schedule_group_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package resources

import (
"context"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/scheduler"
"github.com/gruntwork-io/cloud-nuke/config"
"github.com/gruntwork-io/go-commons/errors"
)

type EventBridgeScheduleGroupAPI interface {
DeleteScheduleGroup(ctx context.Context, params *scheduler.DeleteScheduleGroupInput, optFns ...func(*scheduler.Options)) (*scheduler.DeleteScheduleGroupOutput, error)
ListScheduleGroups(ctx context.Context, params *scheduler.ListScheduleGroupsInput, optFns ...func(*scheduler.Options)) (*scheduler.ListScheduleGroupsOutput, error)
}

type EventBridgeScheduleGroup struct {
BaseAwsResource
Client EventBridgeScheduleGroupAPI
Region string
Groups []string
}

func (sch *EventBridgeScheduleGroup) GetAndSetResourceConfig(configObj config.Config) config.ResourceType {
return configObj.EventBridgeScheduleGroup
}

func (sch *EventBridgeScheduleGroup) InitV2(cfg aws.Config) {
sch.Client = scheduler.NewFromConfig(cfg)
}

func (sch *EventBridgeScheduleGroup) IsUsingV2() bool { return true }

func (sch *EventBridgeScheduleGroup) ResourceName() string { return "event-bridge-schedule-group" }

func (sch *EventBridgeScheduleGroup) ResourceIdentifiers() []string { return sch.Groups }

func (sch *EventBridgeScheduleGroup) MaxBatchSize() int {
return 100
}

func (sch *EventBridgeScheduleGroup) Nuke(identifiers []string) error {
if err := sch.nukeAll(aws.StringSlice(identifiers)); err != nil {
return errors.WithStackTrace(err)
}

return nil
}

func (sch *EventBridgeScheduleGroup) GetAndSetIdentifiers(ctx context.Context, cnfObj config.Config) ([]string, error) {
identifiers, err := sch.getAll(ctx, cnfObj)
if err != nil {
return nil, err
}

sch.Groups = aws.ToStringSlice(identifiers)
return sch.Groups, nil
}
Loading