Repository: gruntwork-io/cloud-nuke
Pull request #767 (2024-09-18 12:24:03)

add support for eventBridge, rules & Archive.
<!-- Prepend '[WIP]' to the title if this PR is still a work-in-progress. Remove it when it is ready for review! -->

## Description

add support to [Amazon EventBridge](https://docs.aws.amazon.com/eventbridge/) `event bus`, `archive` & `rule(s)`.

<!-- Description of the changes introduced by this PR. -->

## TODOs

Read the [Gruntwork contribution guidelines](https://gruntwork.notion.site/Gruntwork-Coding-Methodology-02fdcd6e4b004e818553684760bf691e).

- [x] Update the docs.
- [x] Run the relevant tests successfully, including pre-commit checks.
- [x] Ensure any 3rd party code adheres with our [license policy](https://www.notion.so/gruntwork/Gruntwork-licenses-and-open-source-usage-policy-f7dece1f780341c7b69c1763f22b1378) or delete this line if its not applicable.
- [x] Include release notes. If this PR is backward incompatible, include a migration guide.
- [ ] Attention Grunts - if this PR adds support for a new resource, ensure the `nuke_sandbox` and `nuke_phxdevops` jobs in `.circleci/config.yml` have been updated with appropriate exclusions (either directly in the job or via the `.circleci/nuke_config.yml` file) to prevent nuking IAM roles, groups, resources, etc that are important for the test accounts.


## Release Notes (draft)

Added  support to [Amazon EventBridge](https://docs.aws.amazon.com/eventbridge/) `event bus`, `archive` & `rule(s)`.

### Migration Guide

n/a




Patch:
diff --git a/README.md b/README.md
index 507f004..f20c10f 100644
--- a/README.md
+++ b/README.md
@@ -42,6 +42,9 @@ Cloud-nuke suppports 🔎 inspecting and 🔥💀 deleting the following AWS res
 | EC2                     | Security Group                                           |
 | EC2                     | Network Interface                                        |
 | EC2                     | Placement Group                                          |
+| Event Bridge            | Event buses                                              |
+| Event Bridge            | Archive                                                  |
+| Event Bridge            | Rule                                                     |
 | Certificate Manager     | ACM Private CA                                           |
 | Direct Connect          | Transit Gateways                                         |
 | Elasticache             | Clusters                                                 |
@@ -593,6 +596,9 @@ of the file that are supported are listed here.
 | elasticache                      | Elasticache                   | ✅ (Cluster ID & Replication Group ID) | ✅ (Creation Time)                   | ❌    | ✅       |
 | elasticacheparametergroups       | ElasticacheParameterGroups    | ✅ (Parameter Group Name)              | ❌                                   | ❌    | ✅       |
 | elasticachesubnetgroups          | ElasticacheSubnetGroups       | ✅ (Subnet Group Name)                 | ❌                                   | ❌    | ✅       |
+| event-bridge                     | EventBridge                   | ✅ (Bus  Name)                         | ✅ (Creation Time)                   | ❌    | ✅       |
+| event-bridge-archive             | EventBridgeArchive            | ✅ (Archive Name)                      | ✅ (Creation Time)                   | ❌    | ✅       |
+| event-bridge-rule                | EventBridgeRule               | ✅ (Bus Rule Name)                     | ❌                                   | ❌    | ✅       |
 | guardduty                        | GuardDuty                     | ❌                                     | ✅ (Created Time)                    | ❌    | ✅       |
 | iam-group                        | IAMGroups                     | ✅ (Group Name)                        | ✅ (Creation Time)                   | ❌    | ✅       |
 | iam-policy                       | IAMPolicies                   | ✅ (Policy Name)                       | ✅ (Creation Time)                   | ❌    | ✅       |
diff --git a/aws/resource_registry.go b/aws/resource_registry.go
index 60f8e62..1699a86 100644
--- a/aws/resource_registry.go
+++ b/aws/resource_registry.go
@@ -64,6 +64,9 @@ func getRegisteredRegionalResources() []AwsResource {
 		&resources.AppRunnerService{},
 		&resources.BackupVault{},
 		&resources.ManagedPrometheus{},
+		&resources.EventBridgeArchive{},
+		&resources.EventBridgeRule{},
+		&resources.EventBridge{},
 		&resources.CloudtrailTrail{},
 		&resources.CloudWatchAlarms{},
 		&resources.CloudWatchDashboards{},
diff --git a/aws/resources/eventbridge.go b/aws/resources/eventbridge.go
new file mode 100644
index 0000000..334d06e
--- /dev/null
+++ b/aws/resources/eventbridge.go
@@ -0,0 +1,88 @@
+package resources
+
+import (
+	"context"
+
+	"github.com/aws/aws-sdk-go-v2/aws"
+	"github.com/aws/aws-sdk-go-v2/service/eventbridge"
+	"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 (eb *EventBridge) nukeAll(identifiers []*string) error {
+	if len(identifiers) == 0 {
+		logging.Debugf("[Event Bridge] No Event Bridges found in region %s", eb.Region)
+		return nil
+	}
+
+	logging.Debugf("[Event Bridge] Deleting all Event Bridges in %s", eb.Region)
+	var deleted []*string
+
+	for _, identifier := range identifiers {
+		if *identifier == "default" {
+			logging.Debugf("[Event Bridge] skipping deleteing default Bus in region %s", eb.Region)
+			continue
+		}
+
+		_, err := eb.Client.DeleteEventBus(eb.Context, &eventbridge.DeleteEventBusInput{
+			Name: identifier,
+		})
+		if err != nil {
+			logging.Debugf(
+				"[Event Bridge] Error deleting Bus %s in region %s, err %s",
+				*identifier,
+				eb.Region,
+				err,
+			)
+		} else {
+			deleted = append(deleted, identifier)
+			logging.Debugf("[Event Bridge] Deleted Bus %s in region %s", *identifier, eb.Region)
+		}
+
+		e := report.Entry{
+			Identifier:   aws.ToString(identifier),
+			ResourceType: eb.ResourceName(),
+			Error:        err,
+		}
+		report.Record(e)
+	}
+
+	logging.Debugf("[OK] %d Event Bridges Bus(es) deleted in %s", len(deleted), eb.Region)
+	return nil
+}
+
+func (eb *EventBridge) getAll(ctx context.Context, cnfObj config.Config) ([]*string, error) {
+	var identifiers []*string
+
+	hasMorePages := true
+	params := &eventbridge.ListEventBusesInput{}
+
+	for hasMorePages {
+		buses, err := eb.Client.ListEventBuses(ctx, params)
+		if err != nil {
+			logging.Debugf("[Event Bridge] Failed to list event buses: %s", err)
+			return nil, errors.WithStackTrace(err)
+		}
+
+		for _, bus := range buses.EventBuses {
+			if *bus.Name == "default" {
+				logging.Debugf("[Event Bridge] skipping default event bus in region %s", eb.Region)
+				continue
+			}
+
+			if cnfObj.EventBridge.ShouldInclude(config.ResourceValue{
+				Name: bus.Name,
+				Time: bus.CreationTime,
+			}) {
+				identifiers = append(identifiers, bus.Name)
+			}
+		}
+
+		params.NextToken = buses.NextToken
+		hasMorePages = params.NextToken != nil
+	}
+
+	return identifiers, nil
+}
diff --git a/aws/resources/eventbridge_archive.go b/aws/resources/eventbridge_archive.go
new file mode 100644
index 0000000..be3a81c
--- /dev/null
+++ b/aws/resources/eventbridge_archive.go
@@ -0,0 +1,78 @@
+package resources
+
+import (
+	"context"
+
+	"github.com/aws/aws-sdk-go-v2/aws"
+	"github.com/aws/aws-sdk-go-v2/service/eventbridge"
+	"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 (eba *EventBridgeArchive) getAll(ctx context.Context, cnfObj config.Config) ([]*string, error) {
+	var identifiers []*string
+
+	params := eventbridge.ListArchivesInput{}
+	hasMorePages := true
+
+	for hasMorePages {
+		archives, err := eba.Client.ListArchives(ctx, &params)
+		if err != nil {
+			logging.Debugf("[Event Bridge Archives] Failed to list archives: %s", err)
+			return nil, errors.WithStackTrace(err)
+		}
+
+		for _, archive := range archives.Archives {
+			if cnfObj.EventBridgeArchive.ShouldInclude(config.ResourceValue{
+				Name: archive.ArchiveName,
+				Time: archive.CreationTime,
+			}) {
+				identifiers = append(identifiers, archive.ArchiveName)
+			}
+		}
+
+		params.NextToken = archives.NextToken
+		hasMorePages = params.NextToken != nil
+	}
+
+	return identifiers, nil
+}
+
+func (eba *EventBridgeArchive) nukeAll(identifiers []*string) error {
+	if len(identifiers) == 0 {
+		logging.Debugf("[Event Bridge Archive] No Archives found in region %s", eba.Region)
+		return nil
+	}
+
+	logging.Debugf("[Event Bridge Archive] Deleting all Archives in %s", eba.Region)
+	var deleted []*string
+
+	for _, identifier := range identifiers {
+		_, err := eba.Client.DeleteArchive(context.Background(), &eventbridge.DeleteArchiveInput{
+			ArchiveName: identifier,
+		})
+		if err != nil {
+			logging.Debugf(
+				"[Event Bridge Archive] Error deleting Archive %s in region %s, err %s",
+				*identifier,
+				eba.Region,
+				err,
+			)
+		} else {
+			deleted = append(deleted, identifier)
+			logging.Debugf("[Event Bridge Archive] Deleted Archive %s in region %s", *identifier, eba.Region)
+		}
+
+		e := report.Entry{
+			Identifier:   aws.ToString(identifier),
+			ResourceType: eba.ResourceName(),
+			Error:        err,
+		}
+		report.Record(e)
+	}
+
+	logging.Debugf("[OK] %d Event Bridge Archive(s) deleted in %s", len(deleted), eba.Region)
+	return nil
+}
diff --git a/aws/resources/eventbridge_archive_types.go b/aws/resources/eventbridge_archive_types.go
new file mode 100644
index 0000000..879ccaa
--- /dev/null
+++ b/aws/resources/eventbridge_archive_types.go
@@ -0,0 +1,58 @@
+package resources
+
+import (
+	"context"
+
+	"github.com/aws/aws-sdk-go-v2/aws"
+	"github.com/aws/aws-sdk-go-v2/service/eventbridge"
+	"github.com/gruntwork-io/cloud-nuke/config"
+	"github.com/gruntwork-io/go-commons/errors"
+)
+
+type EventBridgeArchiveAPI interface {
+	ListArchives(ctx context.Context, params *eventbridge.ListArchivesInput, optFns ...func(*eventbridge.Options)) (*eventbridge.ListArchivesOutput, error)
+	DeleteArchive(ctx context.Context, params *eventbridge.DeleteArchiveInput, optFns ...func(*eventbridge.Options)) (*eventbridge.DeleteArchiveOutput, error)
+}
+
+type EventBridgeArchive struct {
+	BaseAwsResource
+	Client EventBridgeArchiveAPI
+	Region string
+	Rules  []string
+}
+
+func (eba *EventBridgeArchive) GetAndSetResourceConfig(configObj config.Config) config.ResourceType {
+	return configObj.EventBridgeArchive
+}
+
+func (eba *EventBridgeArchive) InitV2(cfg aws.Config) {
+	eba.Client = eventbridge.NewFromConfig(cfg)
+}
+
+func (eba *EventBridgeArchive) IsUsingV2() bool { return true }
+
+func (eba *EventBridgeArchive) ResourceName() string { return "event-bridge-archive" }
+
+func (eba *EventBridgeArchive) ResourceIdentifiers() []string { return eba.Rules }
+
+func (eba *EventBridgeArchive) MaxBatchSize() int {
+	return 100
+}
+
+func (eba *EventBridgeArchive) Nuke(identifiers []string) error {
+	if err := eba.nukeAll(aws.StringSlice(identifiers)); err != nil {
+		return errors.WithStackTrace(err)
+	}
+
+	return nil
+}
+
+func (eba *EventBridgeArchive) GetAndSetIdentifiers(ctx context.Context, cnfObj config.Config) ([]string, error) {
+	identifiers, err := eba.getAll(ctx, cnfObj)
+	if err != nil {
+		return nil, err
+	}
+
+	eba.Rules = aws.ToStringSlice(identifiers)
+	return eba.Rules, nil
+}
diff --git a/aws/resources/eventbridge_rule.go b/aws/resources/eventbridge_rule.go
new file mode 100644
index 0000000..6e532b6
--- /dev/null
+++ b/aws/resources/eventbridge_rule.go
@@ -0,0 +1,173 @@
+package resources
+
+import (
+	"context"
+	"fmt"
+	"strings"
+
+	"github.com/aws/aws-sdk-go-v2/aws"
+	"github.com/aws/aws-sdk-go-v2/service/eventbridge"
+	"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 (ebr *EventBridgeRule) getTargets(busName, rule string) ([]string, error) {
+	var identifiers []*string
+
+	params := &eventbridge.ListTargetsByRuleInput{
+		Rule:         aws.String(rule),
+		EventBusName: aws.String(busName),
+		NextToken:    nil,
+	}
+	hasMorePages := true
+
+	for hasMorePages {
+		targets, err := ebr.Client.ListTargetsByRule(ebr.Context, params)
+		if err != nil {
+			return nil, err
+		}
+
+		for _, target := range targets.Targets {
+			identifiers = append(identifiers, target.Id)
+		}
+
+		params.NextToken = targets.NextToken
+		hasMorePages = params.NextToken != nil
+	}
+
+	return aws.ToStringSlice(identifiers), nil
+}
+
+func (ebr *EventBridgeRule) nukeAll(identifiers []*string) error {
+	if len(identifiers) == 0 {
+		logging.Debugf("[Event Bridge Rule] No Event Bridge Rule(s) found in region %s", ebr.Region)
+		return nil
+	}
+
+	logging.Debugf("[Event Bridge Rule] Deleting all Event Bridge Rule(s) in %s", ebr.Region)
+	var deleted []*string
+
+	for _, identifier := range identifiers {
+		payload := strings.Split(*identifier, "|")
+		if len(payload) != 2 {
+			logging.Debugf("[Event Bridge Rule] Invalid identifier %s", *identifier)
+			continue
+		}
+
+		ids, errGetTarget := ebr.getTargets(payload[0], payload[1])
+		if errGetTarget != nil {
+			logging.Debugf("[Event Bridge Rule] error when listing targets for rule %s, %s", payload[1], errGetTarget)
+			return errGetTarget
+		}
+
+		if len(ids) != 0 {
+			_, errRmTargets := ebr.Client.RemoveTargets(ebr.Context, &eventbridge.RemoveTargetsInput{
+				Ids:          ids,
+				EventBusName: aws.String(payload[0]),
+				Rule:         aws.String(payload[1]),
+				Force:        true,
+			})
+			if errRmTargets != nil {
+				logging.Debugf("[Event Bridge Rule] error when removing rule %s, targets %s", payload[1], errRmTargets)
+				return errRmTargets
+			}
+		}
+
+		_, err := ebr.Client.DeleteRule(ebr.Context, &eventbridge.DeleteRuleInput{
+			EventBusName: aws.String(payload[0]),
+			Name:         aws.String(payload[1]),
+			Force:        true,
+		})
+		if err != nil {
+			logging.Debugf(
+				"[Event Bridge Rule] Error deleting Rule %s in region %s, bus %s, err %s",
+				payload[1],
+				ebr.Region,
+				payload[0],
+				err,
+			)
+		} else {
+			deleted = append(deleted, identifier)
+			logging.Debugf(
+				"[Event Bridge Rule] Deleted Rule %s in region %s, bus %s",
+				payload[1],
+				ebr.Region,
+				payload[0],
+			)
+		}
+
+		e := report.Entry{
+			Identifier:   aws.ToString(identifier),
+			ResourceType: ebr.ResourceName(),
+			Error:        err,
+		}
+		report.Record(e)
+	}
+
+	logging.Debugf("[OK] %d Event Bridge Rule(s) deleted in %s", len(deleted), ebr.Region)
+	return nil
+}
+
+func (ebr *EventBridgeRule) getBusNames() ([]*string, error) {
+	var identifiers []*string
+
+	hasMorePages := true
+	params := &eventbridge.ListEventBusesInput{}
+
+	for hasMorePages {
+		buses, err := ebr.Client.ListEventBuses(ebr.Context, params)
+		if err != nil {
+			return nil, err
+		}
+
+		for _, bus := range buses.EventBuses {
+			identifiers = append(identifiers, bus.Name)
+		}
+
+		params.NextToken = buses.NextToken
+		hasMorePages = params.NextToken != nil
+	}
+
+	return identifiers, nil
+}
+
+func (ebr *EventBridgeRule) getAll(ctx context.Context, cnfObj config.Config) ([]*string, error) {
+	var identifiers []*string
+
+	eBusNames, errGetBus := ebr.getBusNames()
+	if errGetBus != nil {
+		logging.Debugf("[Event Bridge] Failed to list event buses: %s", errGetBus)
+		return nil, errors.WithStackTrace(errGetBus)
+	}
+
+	for _, bus := range eBusNames {
+		hasMorePages := true
+		params := &eventbridge.ListEventBusesInput{}
+
+		for hasMorePages {
+			rules, err := ebr.Client.ListRules(ctx, &eventbridge.ListRulesInput{
+				EventBusName: bus,
+			})
+			if err != nil {
+				logging.Debugf("[Event Bridge] Failed to list event rules: %s", err)
+				return nil, errors.WithStackTrace(err)
+			}
+
+			for _, rule := range rules.Rules {
+				id := aws.String(fmt.Sprintf("%s|%s", *bus, *rule.Name))
+				if cnfObj.EventBridgeRule.ShouldInclude(config.ResourceValue{
+					Name: id,
+				}) {
+					identifiers = append(identifiers, id)
+				}
+			}
+
+			params.NextToken = rules.NextToken
+			hasMorePages = params.NextToken != nil
+		}
+	}
+
+	return identifiers, nil
+}
diff --git a/aws/resources/eventbridge_rule_types.go b/aws/resources/eventbridge_rule_types.go
new file mode 100644
index 0000000..238182e
--- /dev/null
+++ b/aws/resources/eventbridge_rule_types.go
@@ -0,0 +1,61 @@
+package resources
+
+import (
+	"context"
+
+	"github.com/aws/aws-sdk-go-v2/aws"
+	"github.com/aws/aws-sdk-go-v2/service/eventbridge"
+	"github.com/gruntwork-io/cloud-nuke/config"
+	"github.com/gruntwork-io/go-commons/errors"
+)
+
+type EventBridgeRuleAPI interface {
+	ListRules(ctx context.Context, params *eventbridge.ListRulesInput, optFns ...func(*eventbridge.Options)) (*eventbridge.ListRulesOutput, error)
+	DeleteRule(ctx context.Context, params *eventbridge.DeleteRuleInput, optFns ...func(*eventbridge.Options)) (*eventbridge.DeleteRuleOutput, error)
+	ListEventBuses(ctx context.Context, params *eventbridge.ListEventBusesInput, optFns ...func(*eventbridge.Options)) (*eventbridge.ListEventBusesOutput, error)
+	ListTargetsByRule(ctx context.Context, params *eventbridge.ListTargetsByRuleInput, optFns ...func(*eventbridge.Options)) (*eventbridge.ListTargetsByRuleOutput, error)
+	RemoveTargets(ctx context.Context, params *eventbridge.RemoveTargetsInput, optFns ...func(*eventbridge.Options)) (*eventbridge.RemoveTargetsOutput, error)
+}
+
+type EventBridgeRule struct {
+	BaseAwsResource
+	Client EventBridgeRuleAPI
+	Region string
+	Rules  []string
+}
+
+func (ebr *EventBridgeRule) GetAndSetResourceConfig(configObj config.Config) config.ResourceType {
+	return configObj.EventBridgeRule
+}
+
+func (ebr *EventBridgeRule) InitV2(cfg aws.Config) {
+	ebr.Client = eventbridge.NewFromConfig(cfg)
+}
+
+func (ebr *EventBridgeRule) IsUsingV2() bool { return true }
+
+func (ebr *EventBridgeRule) ResourceName() string { return "event-bridge-rule" }
+
+func (ebr *EventBridgeRule) ResourceIdentifiers() []string { return ebr.Rules }
+
+func (ebr *EventBridgeRule) MaxBatchSize() int {
+	return 100
+}
+
+func (ebr *EventBridgeRule) Nuke(identifiers []string) error {
+	if err := ebr.nukeAll(aws.StringSlice(identifiers)); err != nil {
+		return errors.WithStackTrace(err)
+	}
+
+	return nil
+}
+
+func (ebr *EventBridgeRule) GetAndSetIdentifiers(ctx context.Context, cnfObj config.Config) ([]string, error) {
+	identifiers, err := ebr.getAll(ctx, cnfObj)
+	if err != nil {
+		return nil, err
+	}
+
+	ebr.Rules = aws.ToStringSlice(identifiers)
+	return ebr.Rules, nil
+}
diff --git a/aws/resources/eventbridge_types.go b/aws/resources/eventbridge_types.go
new file mode 100644
index 0000000..18d2cd4
--- /dev/null
+++ b/aws/resources/eventbridge_types.go
@@ -0,0 +1,58 @@
+package resources
+
+import (
+	"context"
+
+	"github.com/aws/aws-sdk-go-v2/aws"
+	"github.com/aws/aws-sdk-go-v2/service/eventbridge"
+	"github.com/gruntwork-io/cloud-nuke/config"
+	"github.com/gruntwork-io/go-commons/errors"
+)
+
+type EventBridgeAPI interface {
+	DeleteEventBus(ctx context.Context, params *eventbridge.DeleteEventBusInput, optFns ...func(*eventbridge.Options)) (*eventbridge.DeleteEventBusOutput, error)
+	ListEventBuses(ctx context.Context, params *eventbridge.ListEventBusesInput, optFns ...func(*eventbridge.Options)) (*eventbridge.ListEventBusesOutput, error)
+}
+
+type EventBridge struct {
+	BaseAwsResource
+	Client     EventBridgeAPI
+	Region     string
+	EventBuses []string
+}
+
+func (eb *EventBridge) GetAndSetResourceConfig(configObj config.Config) config.ResourceType {
+	return configObj.EventBridge
+}
+
+func (eb *EventBridge) InitV2(cfg aws.Config) {
+	eb.Client = eventbridge.NewFromConfig(cfg)
+}
+
+func (eb *EventBridge) IsUsingV2() bool { return true }
+
+func (eb *EventBridge) ResourceName() string { return "event-bridge" }
+
+func (eb *EventBridge) ResourceIdentifiers() []string { return eb.EventBuses }
+
+func (eb *EventBridge) MaxBatchSize() int {
+	return 100
+}
+
+func (eb *EventBridge) Nuke(identifiers []string) error {
+	if err := eb.nukeAll(aws.StringSlice(identifiers)); err != nil {
+		return errors.WithStackTrace(err)
+	}
+
+	return nil
+}
+
+func (eb *EventBridge) GetAndSetIdentifiers(ctx context.Context, cnfObj config.Config) ([]string, error) {
+	identifiers, err := eb.getAll(ctx, cnfObj)
+	if err != nil {
+		return nil, err
+	}
+
+	eb.EventBuses = aws.ToStringSlice(identifiers)
+	return eb.EventBuses, nil
+}
diff --git a/config/config.go b/config/config.go
index fddc341..a34374c 100644
--- a/config/config.go
+++ b/config/config.go
@@ -71,6 +71,9 @@ type Config struct {
 	Elasticache                     ResourceType               `yaml:"Elasticache"`
 	ElasticacheParameterGroups      ResourceType               `yaml:"ElasticacheParameterGroups"`
 	ElasticacheSubnetGroups         ResourceType               `yaml:"ElasticacheSubnetGroups"`
+	EventBridge                     ResourceType               `yaml:"EventBridge"`
+	EventBridgeArchive              ResourceType               `yaml:"EventBridgeArchive"`
+	EventBridgeRule                 ResourceType               `yaml:"EventBridgeRule"`
 	GuardDuty                       ResourceType               `yaml:"GuardDuty"`
 	IAMGroups                       ResourceType               `yaml:"IAMGroups"`
 	IAMPolicies                     ResourceType               `yaml:"IAMPolicies"`
diff --git a/go.mod b/go.mod
index c43337d..386eb5f 100644
--- a/go.mod
+++ b/go.mod
@@ -4,10 +4,11 @@ go 1.21
 
 require (
 	github.com/aws/aws-sdk-go v1.49.13
-	github.com/aws/aws-sdk-go-v2 v1.30.1
+	github.com/aws/aws-sdk-go-v2 v1.30.5
 	github.com/aws/aws-sdk-go-v2/config v1.27.24
 	github.com/aws/aws-sdk-go-v2/credentials v1.17.24
 	github.com/aws/aws-sdk-go-v2/service/amp v1.27.1
+	github.com/aws/aws-sdk-go-v2/service/eventbridge v1.33.7
 	github.com/charmbracelet/lipgloss v0.6.0
 	github.com/go-errors/errors v1.4.2
 	github.com/gruntwork-io/go-commons v0.17.0
@@ -24,15 +25,16 @@ require (
 	atomicgo.dev/cursor v0.1.1 // indirect
 	atomicgo.dev/keyboard v0.2.8 // indirect
 	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9 // indirect
-	github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13 // indirect
-	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 // indirect
 	github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17 // indirect
 	github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect
 	github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15 // indirect
 	github.com/aws/aws-sdk-go-v2/service/sso v1.22.1 // indirect
 	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2 // indirect
 	github.com/aws/aws-sdk-go-v2/service/sts v1.30.1 // indirect
-	github.com/aws/smithy-go v1.20.3 // indirect
+	github.com/aws/smithy-go v1.20.4 // indirect
 	github.com/containerd/console v1.0.3 // indirect
 	github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
diff --git a/go.sum b/go.sum
index 9d2f7ba..522d496 100644
--- a/go.sum
+++ b/go.sum
@@ -15,22 +15,26 @@ github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYew
 github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk=
 github.com/aws/aws-sdk-go v1.49.13 h1:f4mGztsgnx2dR9r8FQYa9YW/RsKb+N7bgef4UGrOW1Y=
 github.com/aws/aws-sdk-go v1.49.13/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
-github.com/aws/aws-sdk-go-v2 v1.30.1 h1:4y/5Dvfrhd1MxRDD77SrfsDaj8kUkkljU7XE83NPV+o=
-github.com/aws/aws-sdk-go-v2 v1.30.1/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc=
+github.com/aws/aws-sdk-go-v2 v1.30.5 h1:mWSRTwQAb0aLE17dSzztCVJWI9+cRMgqebndjwDyK0g=
+github.com/aws/aws-sdk-go-v2 v1.30.5/go.mod h1:CT+ZPWXbYrci8chcARI3OmI/qgd+f6WtuLOoaIA8PR0=
 github.com/aws/aws-sdk-go-v2/config v1.27.24 h1:NM9XicZ5o1CBU/MZaHwFtimRpWx9ohAUAqkG6AqSqPo=
 github.com/aws/aws-sdk-go-v2/config v1.27.24/go.mod h1:aXzi6QJTuQRVVusAO8/NxpdTeTyr/wRcybdDtfUwJSs=
 github.com/aws/aws-sdk-go-v2/credentials v1.17.24 h1:YclAsrnb1/GTQNt2nzv+756Iw4mF8AOzcDfweWwwm/M=
 github.com/aws/aws-sdk-go-v2/credentials v1.17.24/go.mod h1:Hld7tmnAkoBQdTMNYZGzztzKRdA4fCdn9L83LOoigac=
 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9 h1:Aznqksmd6Rfv2HQN9cpqIV/lQRMaIpJkLLaJ1ZI76no=
 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9/go.mod h1:WQr3MY7AxGNxaqAtsDWn+fBxmd4XvLkzeqQ8P1VM0/w=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13 h1:5SAoZ4jYpGH4721ZNoS1znQrhOfZinOhc4XuTXx/nVc=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13/go.mod h1:+rdA6ZLpaSeM7tSg/B0IEDinCIBJGmW8rKDFkYpP04g=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13 h1:WIijqeaAO7TYFLbhsZmi2rgLEAtWOC1LhxCAVTJlSKw=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13/go.mod h1:i+kbfa76PQbWw/ULoWnp51EYVWH4ENln76fLQE3lXT8=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 h1:pI7Bzt0BJtYA0N/JEC6B8fJ4RBrEMi1LBrkMdFYNSnQ=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17/go.mod h1:Dh5zzJYMtxfIjYW+/evjQ8uj2OyR/ve2KROHGHlSFqE=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 h1:Mqr/V5gvrhA2gvgnF42Zh5iMiQNcOYthFYwCyrnuWlc=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17/go.mod h1:aLJpZlCmjE+V+KtN1q1uyZkfnUWpQGpbsn89XPKyzfU=
 github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
 github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
+github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17 h1:Roo69qTpfu8OlJ2Tb7pAYVuF0CpuUMB0IYWwYP/4DZM=
+github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17/go.mod h1:NcWPxQzGM1USQggaTVwz6VpqMZPX1CvDJLDh6jnOCa4=
 github.com/aws/aws-sdk-go-v2/service/amp v1.27.1 h1:va9j7MGA8jBlQs9KujMlkSdnC26TuEJXv7ywnFjmTZ8=
 github.com/aws/aws-sdk-go-v2/service/amp v1.27.1/go.mod h1:QhmX7qjomjuHJjCJbW+QRdaCBM42v1eAbWvBVFrT1Vw=
+github.com/aws/aws-sdk-go-v2/service/eventbridge v1.33.7 h1:q+xiPu+Dk5MFC20ZjdGGhbihD39Xsih98epvVjnOjyE=
+github.com/aws/aws-sdk-go-v2/service/eventbridge v1.33.7/go.mod h1:iQCsmx9LyBMyMEkLCBVqnIAz+rfo6/ss3oLcYn26+no=
 github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8=
 github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI=
 github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15 h1:I9zMeF107l0rJrpnHpjEiiTSCKYAIw8mALiXcPsGBiA=
@@ -41,8 +45,8 @@ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2 h1:ORnrOK0C4WmYV/uYt3koHEWB
 github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2/go.mod h1:xyFHA4zGxgYkdD73VeezHt3vSKEG9EmFnGwoKlP00u4=
 github.com/aws/aws-sdk-go-v2/service/sts v1.30.1 h1:+woJ607dllHJQtsnJLi52ycuqHMwlW+Wqm2Ppsfp4nQ=
 github.com/aws/aws-sdk-go-v2/service/sts v1.30.1/go.mod h1:jiNR3JqT15Dm+QWq2SRgh0x0bCNSRP2L25+CqPNpJlQ=
-github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE=
-github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
+github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4=
+github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
 github.com/charmbracelet/lipgloss v0.6.0 h1:1StyZB9vBSOyuZxQUcUwGr17JmojPNm87inij9N3wJY=
 github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk=


Test patch:
diff --git a/aws/resources/eventbridge_archive_test.go b/aws/resources/eventbridge_archive_test.go
new file mode 100644
index 0000000..0e7aa7d
--- /dev/null
+++ b/aws/resources/eventbridge_archive_test.go
@@ -0,0 +1,104 @@
+package resources
+
+import (
+	"context"
+	"regexp"
+	"testing"
+	"time"
+
+	"github.com/aws/aws-sdk-go-v2/service/eventbridge"
+	"github.com/aws/aws-sdk-go-v2/service/eventbridge/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 mockedEventBridgeArchiveService struct {
+	EventBridgeArchiveAPI
+	DeleteArchiveOutput eventbridge.DeleteArchiveOutput
+	ListArchivesOutput  eventbridge.ListArchivesOutput
+}
+
+func (m mockedEventBridgeArchiveService) DeleteArchive(ctx context.Context, params *eventbridge.DeleteArchiveInput, optFns ...func(*eventbridge.Options)) (*eventbridge.DeleteArchiveOutput, error) {
+	return &m.DeleteArchiveOutput, nil
+}
+
+func (m mockedEventBridgeArchiveService) ListArchives(ctx context.Context, params *eventbridge.ListArchivesInput, optFns ...func(*eventbridge.Options)) (*eventbridge.ListArchivesOutput, error) {
+	return &m.ListArchivesOutput, nil
+}
+
+func Test_EventBridgeArchive_GetAll(t *testing.T) {
+	t.Parallel()
+
+	now := time.Now()
+
+	archive1 := "test-archive-1"
+	archive2 := "test-archive-2"
+
+	service := EventBridgeArchive{
+		Client: mockedEventBridgeArchiveService{
+			ListArchivesOutput: eventbridge.ListArchivesOutput{
+				Archives: []types.Archive{
+					{
+						ArchiveName:  aws.String(archive1),
+						CreationTime: &now,
+					},
+					{
+						ArchiveName:  aws.String(archive2),
+						CreationTime: aws.Time(now.Add(time.Hour)),
+					},
+				},
+			},
+		},
+	}
+
+	tests := map[string]struct {
+		configObj config.ResourceType
+		expected  []string
+	}{
+		"emptyFilter": {
+			configObj: config.ResourceType{},
+			expected:  []string{archive1, archive2},
+		},
+		"nameExclusionFilter": {
+			configObj: config.ResourceType{
+				ExcludeRule: config.FilterRule{
+					NamesRegExp: []config.Expression{{
+						RE: *regexp.MustCompile(archive1),
+					}},
+				}},
+			expected: []string{archive2},
+		},
+		"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{EventBridgeArchive: tc.configObj},
+			)
+			require.NoError(t, err)
+			require.Equal(t, tc.expected, aws.StringValueSlice(buses))
+		})
+	}
+}
+
+func Test_EventBridgeArchive_NukeAll(t *testing.T) {
+	t.Parallel()
+
+	archiveName := "test-archive-1"
+	service := EventBridgeArchive{
+		Client: mockedEventBridgeArchiveService{DeleteArchiveOutput: eventbridge.DeleteArchiveOutput{}},
+	}
+
+	err := service.nukeAll([]*string{&archiveName})
+	assert.NoError(t, err)
+}
diff --git a/aws/resources/eventbridge_rule_test.go b/aws/resources/eventbridge_rule_test.go
new file mode 100644
index 0000000..afca2b7
--- /dev/null
+++ b/aws/resources/eventbridge_rule_test.go
@@ -0,0 +1,123 @@
+package resources
+
+import (
+	"context"
+	"fmt"
+	"regexp"
+	"testing"
+	"time"
+
+	"github.com/aws/aws-sdk-go-v2/service/eventbridge"
+	"github.com/aws/aws-sdk-go-v2/service/eventbridge/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 mockedEventBridgeRuleService struct {
+	EventBridgeRuleAPI
+	ListEventBusesOutput    eventbridge.ListEventBusesOutput
+	ListRulesOutput         eventbridge.ListRulesOutput
+	ListTargetsByRuleOutput eventbridge.ListTargetsByRuleOutput
+
+	DeleteRuleOutput eventbridge.DeleteRuleOutput
+}
+
+func (m mockedEventBridgeRuleService) ListEventBuses(ctx context.Context, params *eventbridge.ListEventBusesInput, optFns ...func(*eventbridge.Options)) (*eventbridge.ListEventBusesOutput, error) {
+	return &m.ListEventBusesOutput, nil
+}
+
+func (m mockedEventBridgeRuleService) ListRules(ctx context.Context, params *eventbridge.ListRulesInput, optFns ...func(*eventbridge.Options)) (*eventbridge.ListRulesOutput, error) {
+	return &m.ListRulesOutput, nil
+}
+
+func (m mockedEventBridgeRuleService) ListTargetsByRule(ctx context.Context, params *eventbridge.ListTargetsByRuleInput, optFns ...func(*eventbridge.Options)) (*eventbridge.ListTargetsByRuleOutput, error) {
+	return &m.ListTargetsByRuleOutput, nil
+}
+
+func (m mockedEventBridgeRuleService) DeleteRule(ctx context.Context, params *eventbridge.DeleteRuleInput, optFns ...func(*eventbridge.Options)) (*eventbridge.DeleteRuleOutput, error) {
+	return &m.DeleteRuleOutput, nil
+}
+
+func Test_EventBridgeRule_GetAll(t *testing.T) {
+	t.Parallel()
+
+	now := time.Now()
+
+	rule1 := "rule-1"
+	rule2 := "rule-2"
+	busName := "test-bus"
+	bRule1 := fmt.Sprintf("%s|%s", busName, rule1)
+	bRule2 := fmt.Sprintf("%s|%s", busName, rule2)
+
+	service := EventBridgeRule{
+		Client: mockedEventBridgeRuleService{
+			ListEventBusesOutput: eventbridge.ListEventBusesOutput{
+				EventBuses: []types.EventBus{
+					{
+						Arn:          aws.String(fmt.Sprintf("arn::%s", busName)),
+						CreationTime: &now,
+						Name:         aws.String(busName),
+					},
+				},
+			},
+			ListRulesOutput: eventbridge.ListRulesOutput{
+				Rules: []types.Rule{
+					{
+						EventBusName: aws.String(busName),
+						Name:         aws.String(rule1),
+					},
+					{
+						EventBusName: aws.String(busName),
+						Name:         aws.String(rule2),
+					},
+				},
+			},
+		},
+	}
+
+	tests := map[string]struct {
+		configObj config.ResourceType
+		expected  []string
+	}{
+		"emptyFilter": {
+			configObj: config.ResourceType{},
+			expected:  []string{bRule1, bRule2},
+		},
+		"nameExclusionFilter": {
+			configObj: config.ResourceType{
+				ExcludeRule: config.FilterRule{
+					NamesRegExp: []config.Expression{{
+						RE: *regexp.MustCompile(rule1),
+					}},
+				}},
+			expected: []string{bRule2},
+		},
+	}
+
+	for name, tc := range tests {
+		t.Run(name, func(t *testing.T) {
+			buses, err := service.getAll(
+				context.Background(),
+				config.Config{EventBridgeRule: tc.configObj},
+			)
+			require.NoError(t, err)
+			require.Equal(t, tc.expected, aws.StringValueSlice(buses))
+		})
+	}
+}
+
+func Test_EventBridgeRule_NukeAll(t *testing.T) {
+	t.Parallel()
+
+	ruleName := "bus-name|test-rule-1"
+	service := EventBridgeRule{
+		Client: mockedEventBridgeRuleService{
+			DeleteRuleOutput: eventbridge.DeleteRuleOutput{},
+		},
+	}
+
+	err := service.nukeAll([]*string{&ruleName})
+	assert.NoError(t, err)
+}
diff --git a/aws/resources/eventbridge_test.go b/aws/resources/eventbridge_test.go
new file mode 100644
index 0000000..886ba51
--- /dev/null
+++ b/aws/resources/eventbridge_test.go
@@ -0,0 +1,109 @@
+package resources
+
+import (
+	"context"
+	"fmt"
+	"regexp"
+	"testing"
+	"time"
+
+	"github.com/aws/aws-sdk-go-v2/service/eventbridge"
+	"github.com/aws/aws-sdk-go-v2/service/eventbridge/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 mockedEventBridgeService struct {
+	EventBridgeAPI
+	DeleteEventBusOutput eventbridge.DeleteEventBusOutput
+	ListEventBusesOutput eventbridge.ListEventBusesOutput
+}
+
+func (m mockedEventBridgeService) DeleteEventBus(ctx context.Context, params *eventbridge.DeleteEventBusInput, optFns ...func(*eventbridge.Options)) (*eventbridge.DeleteEventBusOutput, error) {
+	return &m.DeleteEventBusOutput, nil
+}
+
+func (m mockedEventBridgeService) ListEventBuses(ctx context.Context, params *eventbridge.ListEventBusesInput, optFns ...func(*eventbridge.Options)) (*eventbridge.ListEventBusesOutput, error) {
+	return &m.ListEventBusesOutput, nil
+}
+
+func Test_EventBridge_GetAll(t *testing.T) {
+	t.Parallel()
+
+	now := time.Now()
+
+	bus1 := "test-bus-1"
+	bus2 := "test-bus-2"
+
+	service := EventBridge{
+		Client: mockedEventBridgeService{
+			ListEventBusesOutput: eventbridge.ListEventBusesOutput{
+				EventBuses: []types.EventBus{
+					{
+						Arn:          aws.String(fmt.Sprintf("arn::%s", bus1)),
+						CreationTime: &now,
+						Name:         aws.String(bus1),
+					},
+					{
+						Arn:          aws.String(fmt.Sprintf("arn::%s", bus2)),
+						CreationTime: aws.Time(now.Add(time.Hour)),
+						Name:         aws.String(bus2),
+					},
+				},
+			},
+		},
+	}
+
+	tests := map[string]struct {
+		configObj config.ResourceType
+		expected  []string
+	}{
+		"emptyFilter": {
+			configObj: config.ResourceType{},
+			expected:  []string{bus1, bus2},
+		},
+		"nameExclusionFilter": {
+			configObj: config.ResourceType{
+				ExcludeRule: config.FilterRule{
+					NamesRegExp: []config.Expression{{
+						RE: *regexp.MustCompile(bus1),
+					}},
+				}},
+			expected: []string{bus2},
+		},
+		"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{EventBridge: tc.configObj},
+			)
+			require.NoError(t, err)
+			require.Equal(t, tc.expected, aws.StringValueSlice(buses))
+		})
+	}
+}
+
+func Test_EventBridge_NukeAll(t *testing.T) {
+	t.Parallel()
+
+	busName := "test-bus-1"
+	service := EventBridge{
+		Client: mockedEventBridgeService{
+			DeleteEventBusOutput: eventbridge.DeleteEventBusOutput{},
+		},
+	}
+
+	err := service.nukeAll([]*string{&busName})
+	assert.NoError(t, err)
+}
diff --git a/config/config_test.go b/config/config_test.go
index 7e47d27..682c93c 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -63,6 +63,9 @@ func emptyConfig() *Config {
 		Elasticache:                     ResourceType{FilterRule{}, FilterRule{}, "", false},
 		ElasticacheParameterGroups:      ResourceType{FilterRule{}, FilterRule{}, "", false},
 		ElasticacheSubnetGroups:         ResourceType{FilterRule{}, FilterRule{}, "", false},
+		EventBridge:                     ResourceType{FilterRule{}, FilterRule{}, "", false},
+		EventBridgeArchive:              ResourceType{FilterRule{}, FilterRule{}, "", false},
+		EventBridgeRule:                 ResourceType{FilterRule{}, FilterRule{}, "", false},
 		GuardDuty:                       ResourceType{FilterRule{}, FilterRule{}, "", false},
 		IAMGroups:                       ResourceType{FilterRule{}, FilterRule{}, "", false},
 		IAMPolicies:                     ResourceType{FilterRule{}, FilterRule{}, "", false},