Repository: gruntwork-io/cloud-nuke
Pull request #646 (2024-02-29 01:19:23)

support route53 services
<!-- Prepend '[WIP]' to the title if this PR is still a work-in-progress. Remove it when it is ready for review! -->

## Description

Fixes https://github.com/gruntwork-io/cloud-nuke/issues/413

<!-- Description of the changes introduced by this PR. -->

## TODOs

Read the [Gruntwork contribution guidelines](https://gruntwork.notion.site/Gruntwork-Coding-Methodology-02fdcd6e4b004e818553684760bf691e).

- [ ] Update the docs.
- [ ] Run the relevant tests successfully, including pre-commit checks.
- [ ] 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.
- [ ] 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)

<!-- One-line description of the PR that can be included in the final release notes. -->
Added / Removed / Updated [X].

### Migration Guide

<!-- Important: If you made any backward incompatible changes, then you must write a migration guide! -->




Patch:
diff --git a/README.md b/README.md
index 522ab23..7d695fb 100644
--- a/README.md
+++ b/README.md
@@ -109,6 +109,9 @@ Cloud-nuke suppports 🔎 inspecting and 🔥💀 deleting the following AWS res
 | SES                     | SES receipt filter                                       |
 | AWS Certificate Manager | Certificates                                             |
 | CodeDeploy              | Applications                                             |
+| Route53                 | Hosted Zones                                             |
+| Route53                 | CIDR collections                                         |
+| Route53                 | Traffic Policies                                         |
 
 > **WARNING:** The RDS APIs also interact with neptune and document db resources.
 > Running `cloud-nuke aws --resource-type rds` without a config file will remove any neptune and document db resources
@@ -515,10 +518,10 @@ of the file that are supported are listed here.
 | ec2-dedicated-hosts         | EC2DedicatedHosts            | ✅ (EC2 Name Tag)                      | ✅ (Allocation Time)                 | ❌    |
 | ec2-dhcp-option             | EC2DhcpOption                | ❌                                     | ❌                                   | ❌    |
 | ec2-keypairs                | EC2KeyPairs                  | ✅ (Key Pair Name)                     | ✅ (Creation Time)                   | ✅    |
-| ec2-ipam                	   | EC2IPAM			                   | ✅ (IPAM name)                    	    | ✅ (Creation Time)                   | ✅    |
-| ec2-ipam-pool               | EC2IPAMPool			               | ✅ (IPAM Pool name)                    | ✅ (Creation Time)                   | ✅    |
-| ec2-ipam-resource-discovery | EC2IPAMResourceDiscovery		   | ✅ (IPAM Discovery Name)               | ✅ (Creation Time)                   | ✅    |
-| ec2-ipam-scope              | EC2IPAMScope		 						        | ✅ (IPAM Scope Name)               		  | ✅ (Creation Time)                   | ✅    |
+| ec2-ipam                	   | EC2IPAM			               | ✅ (IPAM name)                    	   | ✅ (Creation Time)                   | ✅    |
+| ec2-ipam-pool               | EC2IPAMPool			             | ✅ (IPAM Pool name)                    | ✅ (Creation Time)                   | ✅    |
+| ec2-ipam-resource-discovery | EC2IPAMResourceDiscovery		 | ✅ (IPAM Discovery Name)               | ✅ (Creation Time)                   | ✅    |
+| ec2-ipam-scope              | EC2IPAMScope		 						 | ✅ (IPAM Scope Name)               		 | ✅ (Creation Time)                   | ✅    |
 | ecr                         | ECRRepository                | ✅ (Repository Name)                   | ✅ (Creation Time)                   | ❌    |
 | ecscluster                  | ECSCluster                   | ✅ (Cluster Name)                      | ❌                                   | ❌    |
 | ecsserv                     | ECSService                   | ✅ (Service Name)                      | ✅ (Creation Time)                   | ❌    |
@@ -563,6 +566,9 @@ of the file that are supported are listed here.
 | transit-gateway-route-table | TransitGatewayRouteTable     | ❌                                     | ✅ (Creation Time)                   | ❌    |
 | transit-gateway-attachment  | TransitGatewaysVpcAttachment | ❌                                     | ✅ (Creation Time)                   | ❌    |
 | vpc                         | VPC                          | ✅ (EC2 Name Tag)                      | ✅ (First Seen Tag Time)             | ❌    |
+| route53-hosted-zone         | Route53HostedZone            | ✅ (Hosted zone name)                  | ❌                                   | ❌    |
+| route53-cidr-collection     | Route53CIDRCollection        | ✅ (Cidr collection name)              | ❌                                   | ❌    |
+| route53-traffic-policy      | Route53TrafficPolicy         | ✅ (Traffic policy name)               | ❌                                   | ❌    |
 
 ### How to Use
 
diff --git a/aws/resource_registry.go b/aws/resource_registry.go
index d909c69..63a8a9c 100644
--- a/aws/resource_registry.go
+++ b/aws/resource_registry.go
@@ -119,6 +119,9 @@ func getRegisteredRegionalResources() []AwsResource {
 		&resources.EC2IPAMPool{},
 		&resources.EC2IPAMByoasn{},
 		&resources.EC2IPAMCustomAllocation{},
+		&resources.Route53HostedZone{},
+		&resources.Route53CidrCollection{},
+		&resources.Route53TrafficPolicy{},
 	}
 }
 
diff --git a/aws/resources/apigateway.go b/aws/resources/apigateway.go
index 1b645ff..f443a87 100644
--- a/aws/resources/apigateway.go
+++ b/aws/resources/apigateway.go
@@ -78,12 +78,54 @@ func (gateway *ApiGateway) nukeAll(identifiers []*string) error {
 	return nil
 }
 
+func (gateway *ApiGateway) getAttachedStageClientCerts(apigwID *string) ([]*string, error) {
+	var clientCerts []*string
+
+	// remove the client certificate attached with the stages
+	stages, err := gateway.Client.GetStages(&apigateway.GetStagesInput{
+		RestApiId: apigwID,
+	})
+
+	if err != nil {
+		return clientCerts, err
+	}
+	// get the stages attached client certificates
+	for _, stage := range stages.Item {
+		clientCerts = append(clientCerts, stage.ClientCertificateId)
+	}
+	return clientCerts, nil
+}
+
+func (gateway *ApiGateway) removeAttachedClientCertificates(clientCerts []*string) error {
+
+	for _, cert := range clientCerts {
+		logging.Debugf("Deleting Client Certificate %s", *cert)
+		_, err := gateway.Client.DeleteClientCertificate(&apigateway.DeleteClientCertificateInput{
+			ClientCertificateId: cert,
+		})
+		if err != nil {
+			logging.Errorf("[Failed] Error deleting Client Certificate %s", *cert)
+			return err
+		}
+	}
+	return nil
+}
 func (gateway *ApiGateway) nukeAsync(
 	wg *sync.WaitGroup, errChan chan error, apigwID *string) {
 	defer wg.Done()
 
+	// get the attached client certificates
+	clientCerts, err := gateway.getAttachedStageClientCerts(apigwID)
+
 	input := &apigateway.DeleteRestApiInput{RestApiId: apigwID}
-	_, err := gateway.Client.DeleteRestApi(input)
+	_, err = gateway.Client.DeleteRestApi(input)
+
+	// When the rest-api endpoint delete successfully, then remove attached client certs
+	if err == nil {
+		err = gateway.removeAttachedClientCertificates(clientCerts)
+	}
+
+	// send the error data to channel
 	errChan <- err
 
 	// Record status of this resource
diff --git a/aws/resources/route53_cidr_collection.go b/aws/resources/route53_cidr_collection.go
new file mode 100644
index 0000000..c9caa80
--- /dev/null
+++ b/aws/resources/route53_cidr_collection.go
@@ -0,0 +1,111 @@
+package resources
+
+import (
+	"context"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/route53"
+	"github.com/gruntwork-io/cloud-nuke/config"
+	"github.com/gruntwork-io/cloud-nuke/logging"
+	"github.com/gruntwork-io/cloud-nuke/report"
+)
+
+func (r *Route53CidrCollection) getAll(c context.Context, configObj config.Config) ([]*string, error) {
+	var ids []*string
+
+	result, err := r.Client.ListCidrCollections(&route53.ListCidrCollectionsInput{})
+	if err != nil {
+		logging.Errorf("[Failed] unable to list cidr collection: %s", err)
+		return nil, err
+	}
+
+	for _, r := range result.CidrCollections {
+		if configObj.Route53CIDRCollection.ShouldInclude(config.ResourceValue{
+			Name: r.Name,
+		}) {
+			ids = append(ids, r.Id)
+		}
+	}
+	return ids, nil
+}
+
+func (r *Route53CidrCollection) nukeCidrLocations(id *string) (err error) {
+	// get attached cidr blocks
+	loc, err := r.Client.ListCidrBlocks(&route53.ListCidrBlocksInput{
+		CollectionId: id,
+	})
+	if err != nil {
+		logging.Errorf("[Failed] unable to list cidr blocks: %v", err)
+		return err
+	}
+
+	var changes []*route53.CidrCollectionChange
+	for _, block := range loc.CidrBlocks {
+		changes = append(changes, &route53.CidrCollectionChange{
+			CidrList:     []*string{block.CidrBlock},
+			Action:       aws.String("DELETE_IF_EXISTS"),
+			LocationName: block.LocationName,
+		})
+	}
+
+	_, err = r.Client.ChangeCidrCollection(&route53.ChangeCidrCollectionInput{
+		Id:      id,
+		Changes: changes,
+	})
+	if err != nil {
+		logging.Errorf("[Failed] unable to list cidr collections: %v", err)
+		return err
+	}
+
+	logging.Debugf("[Route53 CIDR location] Successfully nuked cidr location(s)")
+	return nil
+}
+
+func (r *Route53CidrCollection) nukeAll(identifiers []*string) (err error) {
+	if len(identifiers) == 0 {
+		logging.Debugf("No Route53 Cidr collection to nuke in region %s", r.Region)
+		return nil
+	}
+	logging.Debugf("Deleting all Route53 Cidr collection in region %s", r.Region)
+
+	var deletedIds []*string
+	for _, id := range identifiers {
+
+		err := func() error {
+
+			// remove the cidr blocks
+			if err := r.nukeCidrLocations(id); err != nil {
+				return err
+			}
+
+			// delete the cidr collection
+			if _, err = r.Client.DeleteCidrCollection(&route53.DeleteCidrCollectionInput{
+				Id: id,
+			}); err != nil {
+				logging.Errorf("[Failed] unable to nuke the cidr collection: %v ", err)
+				return err
+			}
+
+			return nil
+		}()
+
+		// Record status of this resource
+		e := report.Entry{
+			Identifier:   aws.StringValue(id),
+			ResourceType: "Route53 cidr collection",
+			Error:        err,
+		}
+		report.Record(e)
+
+		if err != nil {
+			logging.Errorf("[Failed] %s: %s", *id, err)
+		} else {
+			deletedIds = append(deletedIds, id)
+			logging.Debugf("Deleted Route53 cidr collection: %s", aws.StringValue(id))
+		}
+	}
+
+	logging.Debugf("[OK] %d Route53 cidr collection(s) deleted in %s", len(deletedIds), r.Region)
+
+	return nil
+}
diff --git a/aws/resources/route53_cidr_collection_types.go b/aws/resources/route53_cidr_collection_types.go
new file mode 100644
index 0000000..96eb198
--- /dev/null
+++ b/aws/resources/route53_cidr_collection_types.go
@@ -0,0 +1,58 @@
+package resources
+
+import (
+	"context"
+
+	awsgo "github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/aws/session"
+	"github.com/aws/aws-sdk-go/service/route53"
+	"github.com/aws/aws-sdk-go/service/route53/route53iface"
+	"github.com/gruntwork-io/cloud-nuke/config"
+	"github.com/gruntwork-io/go-commons/errors"
+)
+
+// Route53CidrCollection - represents all Route53CidrCollection
+type Route53CidrCollection struct {
+	BaseAwsResource
+	Client route53iface.Route53API
+	Region string
+	Ids    []string
+}
+
+func (r *Route53CidrCollection) Init(session *session.Session) {
+	r.Client = route53.New(session)
+}
+
+// ResourceName - the simple name of the aws resource
+func (r *Route53CidrCollection) ResourceName() string {
+	return "route53-cidr-collection"
+}
+
+func (r *Route53CidrCollection) MaxBatchSize() int {
+	// Tentative batch size to ensure AWS doesn't throttle
+	return 49
+}
+
+// ResourceIdentifiers - The ids of the cidr collections
+func (r *Route53CidrCollection) ResourceIdentifiers() []string {
+	return r.Ids
+}
+
+func (r *Route53CidrCollection) GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error) {
+	identifiers, err := r.getAll(c, configObj)
+	if err != nil {
+		return nil, err
+	}
+
+	r.Ids = awsgo.StringValueSlice(identifiers)
+	return r.Ids, nil
+}
+
+// Nuke - nuke 'em all!!!
+func (r *Route53CidrCollection) Nuke(identifiers []string) error {
+	if err := r.nukeAll(awsgo.StringSlice(identifiers)); err != nil {
+		return errors.WithStackTrace(err)
+	}
+
+	return nil
+}
diff --git a/aws/resources/route53_hostedzone.go b/aws/resources/route53_hostedzone.go
new file mode 100644
index 0000000..16b9dfe
--- /dev/null
+++ b/aws/resources/route53_hostedzone.go
@@ -0,0 +1,64 @@
+package resources
+
+import (
+	"context"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/route53"
+	"github.com/gruntwork-io/cloud-nuke/config"
+	"github.com/gruntwork-io/cloud-nuke/logging"
+	"github.com/gruntwork-io/cloud-nuke/report"
+)
+
+func (r *Route53HostedZone) getAll(c context.Context, configObj config.Config) ([]*string, error) {
+	var ids []*string
+
+	result, err := r.Client.ListHostedZones(&route53.ListHostedZonesInput{})
+	if err != nil {
+		logging.Errorf("[Failed] unable to list hosted-zones: %s", err)
+		return nil, err
+	}
+
+	for _, r := range result.HostedZones {
+		if configObj.Route53HostedZone.ShouldInclude(config.ResourceValue{
+			Name: r.Name,
+		}) {
+			ids = append(ids, r.Id)
+		}
+	}
+	return ids, nil
+}
+
+func (r *Route53HostedZone) nukeAll(identifiers []*string) (err error) {
+	if len(identifiers) == 0 {
+		logging.Debugf("No Route53 Hosted Zones to nuke in region %s", r.Region)
+		return nil
+	}
+	logging.Debugf("Deleting all Route53 Hosted Zones in region %s", r.Region)
+
+	var deletedIds []*string
+	for _, id := range identifiers {
+		_, err := r.Client.DeleteHostedZone(&route53.DeleteHostedZoneInput{
+			Id: id,
+		})
+
+		// Record status of this resource
+		e := report.Entry{
+			Identifier:   aws.StringValue(id),
+			ResourceType: "Route53 hosted zone",
+			Error:        err,
+		}
+		report.Record(e)
+
+		if err != nil {
+			logging.Errorf("[Failed] %s: %s", *id, err)
+		} else {
+			deletedIds = append(deletedIds, id)
+			logging.Debugf("Deleted Route53 Hosted Zone: %s", aws.StringValue(id))
+		}
+	}
+
+	logging.Debugf("[OK] %d Route53 hosted zone(s) deleted in %s", len(deletedIds), r.Region)
+
+	return nil
+}
diff --git a/aws/resources/route53_hostedzone_types.go b/aws/resources/route53_hostedzone_types.go
new file mode 100644
index 0000000..7084d32
--- /dev/null
+++ b/aws/resources/route53_hostedzone_types.go
@@ -0,0 +1,58 @@
+package resources
+
+import (
+	"context"
+
+	awsgo "github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/aws/session"
+	"github.com/aws/aws-sdk-go/service/route53"
+	"github.com/aws/aws-sdk-go/service/route53/route53iface"
+	"github.com/gruntwork-io/cloud-nuke/config"
+	"github.com/gruntwork-io/go-commons/errors"
+)
+
+// Route53HostedZone - represents all Route53HostedZone
+type Route53HostedZone struct {
+	BaseAwsResource
+	Client route53iface.Route53API
+	Region string
+	Ids    []string
+}
+
+func (r *Route53HostedZone) Init(session *session.Session) {
+	r.Client = route53.New(session)
+}
+
+// ResourceName - the simple name of the aws resource
+func (r *Route53HostedZone) ResourceName() string {
+	return "route53-hosted-zone"
+}
+
+func (r *Route53HostedZone) MaxBatchSize() int {
+	// Tentative batch size to ensure AWS doesn't throttle
+	return 49
+}
+
+// ResourceIdentifiers - The ids hosted zones
+func (r *Route53HostedZone) ResourceIdentifiers() []string {
+	return r.Ids
+}
+
+func (r *Route53HostedZone) GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error) {
+	identifiers, err := r.getAll(c, configObj)
+	if err != nil {
+		return nil, err
+	}
+
+	r.Ids = awsgo.StringValueSlice(identifiers)
+	return r.Ids, nil
+}
+
+// Nuke - nuke 'em all!!!
+func (r *Route53HostedZone) Nuke(identifiers []string) error {
+	if err := r.nukeAll(awsgo.StringSlice(identifiers)); err != nil {
+		return errors.WithStackTrace(err)
+	}
+
+	return nil
+}
diff --git a/aws/resources/route53_traffic_policy.go b/aws/resources/route53_traffic_policy.go
new file mode 100644
index 0000000..bd2b03c
--- /dev/null
+++ b/aws/resources/route53_traffic_policy.go
@@ -0,0 +1,67 @@
+package resources
+
+import (
+	"context"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/route53"
+	"github.com/gruntwork-io/cloud-nuke/config"
+	"github.com/gruntwork-io/cloud-nuke/logging"
+	"github.com/gruntwork-io/cloud-nuke/report"
+)
+
+func (r *Route53TrafficPolicy) getAll(c context.Context, configObj config.Config) ([]*string, error) {
+	var ids []*string
+
+	result, err := r.Client.ListTrafficPolicies(&route53.ListTrafficPoliciesInput{})
+	if err != nil {
+		logging.Errorf("[Failed] unable to list traffic policies: %v", err)
+		return nil, err
+	}
+
+	for _, summary := range result.TrafficPolicySummaries {
+		if configObj.Route53TrafficPolicy.ShouldInclude(config.ResourceValue{
+			Name: summary.Name,
+		}) {
+			ids = append(ids, summary.Id)
+			// store the corresponding version, as this is required for nuking
+			r.versionMap[*summary.Id] = summary.LatestVersion
+		}
+	}
+	return ids, nil
+}
+
+func (r *Route53TrafficPolicy) nukeAll(identifiers []*string) (err error) {
+	if len(identifiers) == 0 {
+		logging.Debugf("No Route53 Traffic Policy to nuke in region %s", r.Region)
+		return nil
+	}
+	logging.Debugf("Deleting all Route53 Traffic Policy in region %s", r.Region)
+
+	var deletedIds []*string
+	for _, id := range identifiers {
+		_, err := r.Client.DeleteTrafficPolicy(&route53.DeleteTrafficPolicyInput{
+			Id:      id,
+			Version: r.versionMap[*id],
+		})
+
+		// Record status of this resource
+		e := report.Entry{
+			Identifier:   aws.StringValue(id),
+			ResourceType: "Route53 Traffic Policy",
+			Error:        err,
+		}
+		report.Record(e)
+
+		if err != nil {
+			logging.Errorf("[Failed] %s: %s", *id, err)
+		} else {
+			deletedIds = append(deletedIds, id)
+			logging.Debugf("Deleted Route53 Traffic Policy: %s", aws.StringValue(id))
+		}
+	}
+
+	logging.Debugf("[OK] %d Route53 Traffic Policy(s) deleted in %s", len(deletedIds), r.Region)
+
+	return nil
+}
diff --git a/aws/resources/route53_traffic_policy_types.go b/aws/resources/route53_traffic_policy_types.go
new file mode 100644
index 0000000..db8dc33
--- /dev/null
+++ b/aws/resources/route53_traffic_policy_types.go
@@ -0,0 +1,60 @@
+package resources
+
+import (
+	"context"
+
+	awsgo "github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/aws/session"
+	"github.com/aws/aws-sdk-go/service/route53"
+	"github.com/aws/aws-sdk-go/service/route53/route53iface"
+	"github.com/gruntwork-io/cloud-nuke/config"
+	"github.com/gruntwork-io/go-commons/errors"
+)
+
+// Route53TrafficPolicy - represents all Route53TrafficPolicy
+type Route53TrafficPolicy struct {
+	BaseAwsResource
+	Client     route53iface.Route53API
+	Region     string
+	Ids        []string
+	versionMap map[string]*int64
+}
+
+func (r *Route53TrafficPolicy) Init(session *session.Session) {
+	r.Client = route53.New(session)
+	r.versionMap = make(map[string]*int64)
+}
+
+// ResourceName - the simple name of the aws resource
+func (r *Route53TrafficPolicy) ResourceName() string {
+	return "route53-traffic-policy"
+}
+
+func (r *Route53TrafficPolicy) MaxBatchSize() int {
+	// Tentative batch size to ensure AWS doesn't throttle
+	return 49
+}
+
+// ResourceIdentifiers - The ids of traffic policies
+func (r *Route53TrafficPolicy) ResourceIdentifiers() []string {
+	return r.Ids
+}
+
+func (r *Route53TrafficPolicy) GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error) {
+	identifiers, err := r.getAll(c, configObj)
+	if err != nil {
+		return nil, err
+	}
+
+	r.Ids = awsgo.StringValueSlice(identifiers)
+	return r.Ids, nil
+}
+
+// Nuke - nuke 'em all!!!
+func (r *Route53TrafficPolicy) Nuke(identifiers []string) error {
+	if err := r.nukeAll(awsgo.StringSlice(identifiers)); err != nil {
+		return errors.WithStackTrace(err)
+	}
+
+	return nil
+}
diff --git a/config/config.go b/config/config.go
index 9ec5e65..5f7abc4 100644
--- a/config/config.go
+++ b/config/config.go
@@ -91,6 +91,9 @@ type Config struct {
 	TransitGatewaysVpcAttachment    ResourceType               `yaml:"TransitGatewaysVpcAttachment"`
 	TransitGatewayPeeringAttachment ResourceType               `yaml:"TransitGatewayPeeringAttachment"`
 	VPC                             ResourceType               `yaml:"VPC"`
+	Route53HostedZone               ResourceType               `yaml:"Route53HostedZone"`
+	Route53CIDRCollection           ResourceType               `yaml:"Route53CIDRCollection"`
+	Route53TrafficPolicy            ResourceType               `yaml:"Route53TrafficPolicy"`
 }
 
 func (c *Config) addTimeAfterFilter(timeFilter *time.Time, fieldName string) {


Test patch:
diff --git a/aws/resources/apigateway_test.go b/aws/resources/apigateway_test.go
index 61f6925..60083cb 100644
--- a/aws/resources/apigateway_test.go
+++ b/aws/resources/apigateway_test.go
@@ -18,8 +18,10 @@ import (
 
 type mockedApiGateway struct {
 	apigatewayiface.APIGatewayAPI
-	GetRestApisResp   apigateway.GetRestApisOutput
-	DeleteRestApiResp apigateway.DeleteRestApiOutput
+	GetRestApisResp               apigateway.GetRestApisOutput
+	DeleteRestApiResp             apigateway.DeleteRestApiOutput
+	GetStagesOutput               apigateway.GetStagesOutput
+	DeleteClientCertificateOutput apigateway.DeleteClientCertificateOutput
 }
 
 func (m mockedApiGateway) GetRestApis(*apigateway.GetRestApisInput) (*apigateway.GetRestApisOutput, error) {
@@ -31,6 +33,13 @@ func (m mockedApiGateway) DeleteRestApi(*apigateway.DeleteRestApiInput) (*apigat
 	// Only need to return mocked response output
 	return &m.DeleteRestApiResp, nil
 }
+func (m mockedApiGateway) GetStages(*apigateway.GetStagesInput) (*apigateway.GetStagesOutput, error) {
+	return &m.GetStagesOutput, nil
+}
+
+func (m mockedApiGateway) DeleteClientCertificate(*apigateway.DeleteClientCertificateInput) (*apigateway.DeleteClientCertificateOutput, error) {
+	return &m.DeleteClientCertificateOutput, nil
+}
 
 func TestAPIGatewayGetAllAndNukeAll(t *testing.T) {
 	telemetry.InitTelemetry("cloud-nuke", "")
@@ -122,3 +131,40 @@ func TestNukeAPIGatewayMoreThanOne(t *testing.T) {
 	err = apiGateway.nukeAll([]*string{aws.String(testApiID1), aws.String(testApiID2)})
 	require.NoError(t, err)
 }
+
+func TestNukeAPIGatewayWithCertificates(t *testing.T) {
+	telemetry.InitTelemetry("cloud-nuke", "")
+	t.Parallel()
+
+	testApiID1 := "aws-nuke-test-" + util.UniqueID()
+	testApiID2 := "aws-nuke-test-" + util.UniqueID()
+
+	clientCertID := "aws-client-cert" + util.UniqueID()
+	apiGateway := ApiGateway{
+		Client: mockedApiGateway{
+			GetRestApisResp: apigateway.GetRestApisOutput{
+				Items: []*apigateway.RestApi{
+					{Id: aws.String(testApiID1)},
+					{Id: aws.String(testApiID2)},
+				},
+			},
+			GetStagesOutput: apigateway.GetStagesOutput{
+				Item: []*apigateway.Stage{
+					{
+						ClientCertificateId: aws.String(clientCertID),
+					},
+				},
+			},
+			DeleteClientCertificateOutput: apigateway.DeleteClientCertificateOutput{},
+			DeleteRestApiResp:             apigateway.DeleteRestApiOutput{},
+		},
+	}
+
+	apis, err := apiGateway.getAll(context.Background(), config.Config{})
+	require.NoError(t, err)
+	require.Contains(t, awsgo.StringValueSlice(apis), testApiID1)
+	require.Contains(t, awsgo.StringValueSlice(apis), testApiID2)
+
+	err = apiGateway.nukeAll([]*string{aws.String(testApiID1), aws.String(testApiID2)})
+	require.NoError(t, err)
+}
diff --git a/aws/resources/route53_cidr_collection_test.go b/aws/resources/route53_cidr_collection_test.go
new file mode 100644
index 0000000..2bddf53
--- /dev/null
+++ b/aws/resources/route53_cidr_collection_test.go
@@ -0,0 +1,123 @@
+package resources
+
+import (
+	"context"
+	"regexp"
+	"testing"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/route53"
+	"github.com/aws/aws-sdk-go/service/route53/route53iface"
+	"github.com/gruntwork-io/cloud-nuke/config"
+	"github.com/gruntwork-io/cloud-nuke/telemetry"
+	"github.com/stretchr/testify/require"
+)
+
+type mockedR53CidrCollection struct {
+	route53iface.Route53API
+	ListCidrBlocksOutput       route53.ListCidrBlocksOutput
+	ChangeCidrCollectionOutput route53.ChangeCidrCollectionOutput
+	ListCidrCollectionsOutput  route53.ListCidrCollectionsOutput
+	DeleteCidrCollectionOutput route53.DeleteCidrCollectionOutput
+}
+
+func (mock mockedR53CidrCollection) ListCidrBlocks(_ *route53.ListCidrBlocksInput) (*route53.ListCidrBlocksOutput, error) {
+	return &mock.ListCidrBlocksOutput, nil
+}
+
+func (mock mockedR53CidrCollection) ChangeCidrCollection(_ *route53.ChangeCidrCollectionInput) (*route53.ChangeCidrCollectionOutput, error) {
+	return &mock.ChangeCidrCollectionOutput, nil
+}
+func (mock mockedR53CidrCollection) ListCidrCollections(_ *route53.ListCidrCollectionsInput) (*route53.ListCidrCollectionsOutput, error) {
+	return &mock.ListCidrCollectionsOutput, nil
+}
+func (mock mockedR53CidrCollection) DeleteCidrCollection(_ *route53.DeleteCidrCollectionInput) (*route53.DeleteCidrCollectionOutput, error) {
+	return &mock.DeleteCidrCollectionOutput, nil
+}
+
+func TestR53CidrCollection_GetAll(t *testing.T) {
+	telemetry.InitTelemetry("cloud-nuke", "")
+	t.Parallel()
+
+	testId1 := "d8c6f2db-89dd-5533-f30c-13e28eba8818"
+	testId2 := "d8c6f2db-90dd-5533-f30c-13e28eba8818"
+
+	testName1 := "Test name 01"
+	testName2 := "Test name 02"
+	rc := Route53CidrCollection{
+		Client: mockedR53CidrCollection{
+			ListCidrCollectionsOutput: route53.ListCidrCollectionsOutput{
+				CidrCollections: []*route53.CollectionSummary{
+					{
+						Id:   aws.String(testId1),
+						Name: aws.String(testName1),
+					},
+					{
+						Id:   aws.String(testId2),
+						Name: aws.String(testName2),
+					},
+				},
+			},
+		},
+	}
+
+	tests := map[string]struct {
+		configObj config.ResourceType
+		expected  []string
+	}{
+		"emptyFilter": {
+			configObj: config.ResourceType{},
+			expected:  []string{testId1, testId2},
+		},
+		"nameExclusionFilter": {
+			configObj: config.ResourceType{
+				ExcludeRule: config.FilterRule{
+					NamesRegExp: []config.Expression{{
+						RE: *regexp.MustCompile(testName1),
+					}}},
+			},
+			expected: []string{testId2},
+		},
+	}
+
+	for name, tc := range tests {
+		t.Run(name, func(t *testing.T) {
+			names, err := rc.getAll(context.Background(), config.Config{
+				Route53CIDRCollection: tc.configObj,
+			})
+			require.NoError(t, err)
+			require.Equal(t, tc.expected, aws.StringValueSlice(names))
+		})
+	}
+}
+
+func TestR53CidrCollection_Nuke(t *testing.T) {
+	telemetry.InitTelemetry("cloud-nuke", "")
+	t.Parallel()
+
+	rc := Route53CidrCollection{
+		Client: mockedR53CidrCollection{
+			ListCidrBlocksOutput: route53.ListCidrBlocksOutput{
+				CidrBlocks: []*route53.CidrBlockSummary{
+					{
+						CidrBlock:    aws.String("222::0"),
+						LocationName: aws.String("sample-location-01"),
+					},
+				},
+			},
+			ChangeCidrCollectionOutput: route53.ChangeCidrCollectionOutput{},
+			ListCidrCollectionsOutput: route53.ListCidrCollectionsOutput{
+				CidrCollections: []*route53.CollectionSummary{
+					{
+						Id:   aws.String("collection-id-01"),
+						Name: aws.String("collection-name-01"),
+					},
+				},
+			},
+			DeleteCidrCollectionOutput: route53.DeleteCidrCollectionOutput{},
+		},
+	}
+
+	err := rc.nukeAll([]*string{aws.String("collection-id-01")})
+	require.NoError(t, err)
+}
diff --git a/aws/resources/route53_hostedzone_test.go b/aws/resources/route53_hostedzone_test.go
new file mode 100644
index 0000000..eb12b12
--- /dev/null
+++ b/aws/resources/route53_hostedzone_test.go
@@ -0,0 +1,98 @@
+package resources
+
+import (
+	"context"
+	"regexp"
+	"testing"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/route53"
+	"github.com/aws/aws-sdk-go/service/route53/route53iface"
+	"github.com/gruntwork-io/cloud-nuke/config"
+	"github.com/gruntwork-io/cloud-nuke/telemetry"
+	"github.com/stretchr/testify/require"
+)
+
+type mockedR53HostedZone struct {
+	route53iface.Route53API
+	ListHostedZonesOutput  route53.ListHostedZonesOutput
+	DeleteHostedZoneOutput route53.DeleteHostedZoneOutput
+}
+
+func (mock mockedR53HostedZone) ListHostedZones(_ *route53.ListHostedZonesInput) (*route53.ListHostedZonesOutput, error) {
+	return &mock.ListHostedZonesOutput, nil
+}
+
+func (mock mockedR53HostedZone) DeleteHostedZone(_ *route53.DeleteHostedZoneInput) (*route53.DeleteHostedZoneOutput, error) {
+	return &mock.DeleteHostedZoneOutput, nil
+}
+
+func TestR53HostedZone_GetAll(t *testing.T) {
+	telemetry.InitTelemetry("cloud-nuke", "")
+	t.Parallel()
+
+	testId1 := "d8c6f2db-89dd-5533-f30c-13e28eba8818"
+	testId2 := "d8c6f2db-90dd-5533-f30c-13e28eba8818"
+
+	testName1 := "Test name 01"
+	testName2 := "Test name 02"
+	rc := Route53HostedZone{
+		Client: mockedR53HostedZone{
+			ListHostedZonesOutput: route53.ListHostedZonesOutput{
+				HostedZones: []*route53.HostedZone{
+					{
+						Id:   aws.String(testId1),
+						Name: aws.String(testName1),
+					},
+					{
+						Id:   aws.String(testId2),
+						Name: aws.String(testName2),
+					},
+				},
+			},
+		},
+	}
+
+	tests := map[string]struct {
+		configObj config.ResourceType
+		expected  []string
+	}{
+		"emptyFilter": {
+			configObj: config.ResourceType{},
+			expected:  []string{testId1, testId2},
+		},
+		"nameExclusionFilter": {
+			configObj: config.ResourceType{
+				ExcludeRule: config.FilterRule{
+					NamesRegExp: []config.Expression{{
+						RE: *regexp.MustCompile(testName1),
+					}}},
+			},
+			expected: []string{testId2},
+		},
+	}
+
+	for name, tc := range tests {
+		t.Run(name, func(t *testing.T) {
+			names, err := rc.getAll(context.Background(), config.Config{
+				Route53HostedZone: tc.configObj,
+			})
+			require.NoError(t, err)
+			require.Equal(t, tc.expected, aws.StringValueSlice(names))
+		})
+	}
+}
+
+func TestR53HostedZone_Nuke(t *testing.T) {
+	telemetry.InitTelemetry("cloud-nuke", "")
+	t.Parallel()
+
+	rc := Route53HostedZone{
+		Client: mockedR53HostedZone{
+			DeleteHostedZoneOutput: route53.DeleteHostedZoneOutput{},
+		},
+	}
+
+	err := rc.nukeAll([]*string{aws.String("collection-id-01")})
+	require.NoError(t, err)
+}
diff --git a/aws/resources/route53_traffic_policy_test.go b/aws/resources/route53_traffic_policy_test.go
new file mode 100644
index 0000000..871b590
--- /dev/null
+++ b/aws/resources/route53_traffic_policy_test.go
@@ -0,0 +1,102 @@
+package resources
+
+import (
+	"context"
+	"regexp"
+	"testing"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/route53"
+	"github.com/aws/aws-sdk-go/service/route53/route53iface"
+	"github.com/gruntwork-io/cloud-nuke/config"
+	"github.com/gruntwork-io/cloud-nuke/telemetry"
+	"github.com/stretchr/testify/require"
+)
+
+type mockedR53TrafficPolicy struct {
+	route53iface.Route53API
+	ListTrafficPoliciesOutput route53.ListTrafficPoliciesOutput
+	DeleteTrafficPolicyOutput route53.DeleteTrafficPolicyOutput
+}
+
+func (mock mockedR53TrafficPolicy) ListTrafficPolicies(_ *route53.ListTrafficPoliciesInput) (*route53.ListTrafficPoliciesOutput, error) {
+	return &mock.ListTrafficPoliciesOutput, nil
+}
+
+func (mock mockedR53TrafficPolicy) DeleteTrafficPolicy(_ *route53.DeleteTrafficPolicyInput) (*route53.DeleteTrafficPolicyOutput, error) {
+	return &mock.DeleteTrafficPolicyOutput, nil
+}
+
+func TestR53TrafficPolicy_GetAll(t *testing.T) {
+	telemetry.InitTelemetry("cloud-nuke", "")
+	t.Parallel()
+
+	testId1 := "d8c6f2db-89dd-5533-f30c-13e28eba8818"
+	testId2 := "d8c6f2db-90dd-5533-f30c-13e28eba8818"
+
+	testName1 := "Test name 01"
+	testName2 := "Test name 02"
+	rc := Route53TrafficPolicy{
+		Client: mockedR53TrafficPolicy{
+			ListTrafficPoliciesOutput: route53.ListTrafficPoliciesOutput{
+				TrafficPolicySummaries: []*route53.TrafficPolicySummary{
+					{
+						Id:            aws.String(testId1),
+						Name:          aws.String(testName1),
+						LatestVersion: aws.Int64(1),
+					},
+					{
+						Id:            aws.String(testId2),
+						Name:          aws.String(testName2),
+						LatestVersion: aws.Int64(1),
+					},
+				},
+			},
+		},
+		versionMap: make(map[string]*int64),
+	}
+
+	tests := map[string]struct {
+		configObj config.ResourceType
+		expected  []string
+	}{
+		"emptyFilter": {
+			configObj: config.ResourceType{},
+			expected:  []string{testId1, testId2},
+		},
+		"nameExclusionFilter": {
+			configObj: config.ResourceType{
+				ExcludeRule: config.FilterRule{
+					NamesRegExp: []config.Expression{{
+						RE: *regexp.MustCompile(testName1),
+					}}},
+			},
+			expected: []string{testId2},
+		},
+	}
+
+	for name, tc := range tests {
+		t.Run(name, func(t *testing.T) {
+			names, err := rc.getAll(context.Background(), config.Config{
+				Route53TrafficPolicy: tc.configObj,
+			})
+			require.NoError(t, err)
+			require.Equal(t, tc.expected, aws.StringValueSlice(names))
+		})
+	}
+}
+
+func TestR53TrafficPolicy_Nuke(t *testing.T) {
+	telemetry.InitTelemetry("cloud-nuke", "")
+	t.Parallel()
+
+	rc := Route53TrafficPolicy{
+		Client: mockedR53TrafficPolicy{
+			DeleteTrafficPolicyOutput: route53.DeleteTrafficPolicyOutput{},
+		},
+		versionMap: make(map[string]*int64),
+	}
+
+	err := rc.nukeAll([]*string{aws.String("policy-01")})
+	require.NoError(t, err)
+}
diff --git a/config/config_test.go b/config/config_test.go
index a33c7c1..b16abd8 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -89,6 +89,9 @@ func emptyConfig() *Config {
 		ResourceType{FilterRule{}, FilterRule{}},
 		ResourceType{FilterRule{}, FilterRule{}},
 		ResourceType{FilterRule{}, FilterRule{}},
+		ResourceType{FilterRule{}, FilterRule{}},
+		ResourceType{FilterRule{}, FilterRule{}},
+		ResourceType{FilterRule{}, FilterRule{}},
 	}
 }