Skip to content

Commit

Permalink
Refactor the code structure of aws directory (#629)
Browse files Browse the repository at this point in the history
  • Loading branch information
james03160927 authored Dec 20, 2023
1 parent 876c0ad commit 5c9b6fc
Show file tree
Hide file tree
Showing 7 changed files with 233 additions and 223 deletions.
4 changes: 2 additions & 2 deletions aws/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func GetAllResources(c context.Context, query *Query, configObj config.Config) (
configObj.AddIncludeAfterTime(query.IncludeAfter)
configObj.KMSCustomerKeys.IncludeUnaliasedKeys = query.ListUnaliasedKMSKeys
account := AwsAccountResources{
Resources: make(map[string]AwsRegionResource),
Resources: make(map[string]AwsResources),
}

spinner, _ := pterm.DefaultSpinner.WithRemoveWhenDone(true).Start()
Expand All @@ -37,7 +37,7 @@ func GetAllResources(c context.Context, query *Query, configObj config.Config) (
c = context.WithValue(c, util.AccountIdKey, accountId)
}

awsResource := AwsRegionResource{}
awsResource := AwsResources{}
registeredResources := GetAndInitRegisteredResources(cloudNukeSession, region)
for _, resource := range registeredResources {
if IsNukeable((*resource).ResourceName(), query.ResourceTypes) {
Expand Down
58 changes: 58 additions & 0 deletions aws/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package aws

import "fmt"

type CouldNotSelectRegionError struct {
Underlying error
}

func (err CouldNotSelectRegionError) Error() string {
return fmt.Sprintf("Unable to determine target region set. Please double check your combination of target and excluded regions. Original error: %v", err.Underlying)
}

type CouldNotDetermineEnabledRegionsError struct {
Underlying error
}

func (err CouldNotDetermineEnabledRegionsError) Error() string {
return fmt.Sprintf("Unable to determine enabled regions in target account. Original error: %v", err.Underlying)
}

type InvalidResourceTypesSuppliedError struct {
InvalidTypes []string
}

func (err InvalidResourceTypesSuppliedError) Error() string {
return fmt.Sprintf("Invalid resourceTypes %s specified: %s", err.InvalidTypes, "Try --list-resource-types to get a list of valid resource types.")
}

type ResourceTypeAndExcludeFlagsBothPassedError struct{}

func (err ResourceTypeAndExcludeFlagsBothPassedError) Error() string {
return "You can not specify both --resource-type and --exclude-resource-type"
}

type InvalidTimeStringPassedError struct {
Entry string
Underlying error
}

func (err InvalidTimeStringPassedError) Error() string {
return fmt.Sprintf("Could not parse %s as a valid time duration. Underlying error: %s", err.Entry, err.Underlying)
}

type QueryCreationError struct {
Underlying error
}

func (err QueryCreationError) Error() string {
return fmt.Sprintf("Error forming a cloud-nuke Query with supplied parameters. Original error: %v", err.Underlying)
}

type ResourceInspectionError struct {
Underlying error
}

func (err ResourceInspectionError) Error() string {
return fmt.Sprintf("Error encountered when querying for account resources. Original error: %v", err.Underlying)
}
65 changes: 65 additions & 0 deletions aws/query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package aws

import (
"time"
)

// Query is a struct that represents the desired parameters for scanning resources within a given account
type Query struct {
Regions []string
ExcludeRegions []string
ResourceTypes []string
ExcludeResourceTypes []string
ExcludeAfter *time.Time
IncludeAfter *time.Time
ListUnaliasedKMSKeys bool
}

// NewQuery configures and returns a Query struct that can be passed into the InspectResources method
func NewQuery(regions, excludeRegions, resourceTypes, excludeResourceTypes []string, excludeAfter, includeAfter *time.Time, listUnaliasedKMSKeys bool) (*Query, error) {
q := &Query{
Regions: regions,
ExcludeRegions: excludeRegions,
ResourceTypes: resourceTypes,
ExcludeResourceTypes: excludeResourceTypes,
ExcludeAfter: excludeAfter,
IncludeAfter: includeAfter,
ListUnaliasedKMSKeys: listUnaliasedKMSKeys,
}

validationErr := q.Validate()

if validationErr != nil {
return q, validationErr
}

return q, nil
}

// Validate ensures the configured values for a Query are valid, returning an error if there are
// any invalid params, or nil if the Query is valid
func (q *Query) Validate() error {
resourceTypes, err := HandleResourceTypeSelections(q.ResourceTypes, q.ExcludeResourceTypes)
if err != nil {
return err
}

q.ResourceTypes = resourceTypes

regions, err := GetEnabledRegions()
if err != nil {
return CouldNotDetermineEnabledRegionsError{Underlying: err}
}

// global is a fake region, used to represent global resources
regions = append(regions, GlobalRegion)

targetRegions, err := GetTargetRegions(regions, q.Regions, q.ExcludeRegions)
if err != nil {
return CouldNotSelectRegionError{Underlying: err}
}

q.Regions = targetRegions

return nil
}
File renamed without changes.
91 changes: 91 additions & 0 deletions aws/resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package aws

import (
"context"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/gruntwork-io/cloud-nuke/config"
"strings"
)

// AwsResource is an interface that represents a single AWS resource
type AwsResource interface {
Init(session *session.Session)
ResourceName() string
ResourceIdentifiers() []string
MaxBatchSize() int
Nuke(identifiers []string) error
GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error)
}

// AwsResources is a struct to hold multiple instances of AwsResource.
type AwsResources struct {
Resources []*AwsResource
}

// AwsAccountResources is a struct that represents the resources found in a single AWS account
type AwsAccountResources struct {
Resources map[string]AwsResources
}

func (a *AwsAccountResources) GetRegion(region string) AwsResources {
if val, ok := a.Resources[region]; ok {
return val
}
return AwsResources{}
}

// TotalResourceCount returns the number of resources found, that are eligible for nuking, across all AWS regions targeted
// In other words, if you have 3 nukeable resources in us-east-1 and 4 nukeable resources in ap-southeast-1, this function
// would return 7
func (a *AwsAccountResources) TotalResourceCount() int {
total := 0
for _, regionResource := range a.Resources {
for _, resource := range regionResource.Resources {
total += len((*resource).ResourceIdentifiers())
}
}
return total
}

// MapResourceTypeToIdentifiers converts a slice of Resources to a map of resource types to their found identifiers
// For example: ["ec2"] = ["i-0b22a22eec53b9321", "i-0e22a22yec53b9456"]
func (arr *AwsResources) MapResourceTypeToIdentifiers() map[string][]string {
// Initialize map of resource name to identifier, e.g., ["ec2"] = "i-0b22a22eec53b9321"
m := make(map[string][]string)
for _, resource := range arr.Resources {
if len((*resource).ResourceIdentifiers()) > 0 {
for _, id := range (*resource).ResourceIdentifiers() {
m[(*resource).ResourceName()] = append(m[(*resource).ResourceName()], id)
}
}
}
return m
}

// CountOfResourceType is a convenience method that returns the number of the supplied resource type found
// in the AwsResources
func (arr *AwsResources) CountOfResourceType(resourceType string) int {
idMap := arr.MapResourceTypeToIdentifiers()
resourceType = strings.ToLower(resourceType)
if val, ok := idMap[resourceType]; ok {
return len(val)
}
return 0
}

// ResourceTypePresent is a convenience method that returns true, if the given resource is found in the AwsResources,
// or false if it is not
func (arr *AwsResources) ResourceTypePresent(resourceType string) bool {
return arr.CountOfResourceType(resourceType) > 0
}

// IdentifiersForResourceType is a convenience method that returns the list of resource identifiers for a given
// resource type, if available
func (arr *AwsResources) IdentifiersForResourceType(resourceType string) []string {
idMap := arr.MapResourceTypeToIdentifiers()
resourceType = strings.ToLower(resourceType)
if val, ok := idMap[resourceType]; ok {
return val
}
return []string{}
}
34 changes: 17 additions & 17 deletions aws/resource_registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,28 @@ const Global = "global"

// GetAllRegisteredResources - returns a list of all registered resources without initialization.
// This is useful for listing all resources without initializing them.
func GetAllRegisteredResources() []*AwsResources {
resources := getRegisteredGlobalResources()
resources = append(resources, getRegisteredRegionalResources()...)
func GetAllRegisteredResources() []*AwsResource {
registeredResources := getRegisteredGlobalResources()
registeredResources = append(registeredResources, getRegisteredRegionalResources()...)

return toAwsResourcesPointer(resources)
return toAwsResourcesPointer(registeredResources)
}

// GetAndInitRegisteredResources - returns a list of all registered resources with initialization.
func GetAndInitRegisteredResources(session *session.Session, region string) []*AwsResources {
var resources []AwsResources
func GetAndInitRegisteredResources(session *session.Session, region string) []*AwsResource {
var registeredResources []AwsResource
if region == Global {
resources = getRegisteredGlobalResources()
registeredResources = getRegisteredGlobalResources()
} else {
resources = getRegisteredRegionalResources()
registeredResources = getRegisteredRegionalResources()
}

return initRegisteredResources(toAwsResourcesPointer(resources), session, region)
return initRegisteredResources(toAwsResourcesPointer(registeredResources), session, region)
}

// GetRegisteredGlobalResources - returns a list of registered global resources.
func getRegisteredGlobalResources() []AwsResources {
return []AwsResources{
func getRegisteredGlobalResources() []AwsResource {
return []AwsResource{
&resources.IAMUsers{},
&resources.IAMGroups{},
&resources.IAMPolicies{},
Expand All @@ -42,11 +42,11 @@ func getRegisteredGlobalResources() []AwsResources {
}
}

func getRegisteredRegionalResources() []AwsResources {
func getRegisteredRegionalResources() []AwsResource {
// Note: The order is important because it determines the order of nuking resources. Some resources need to
// be deleted before others (Dependencies between resources exist). For example, we want to delete all EC2
// instances before deleting the VPC.
return []AwsResources{
return []AwsResource{
&resources.AccessAnalyzer{},
&resources.ACM{},
&resources.ACMPCA{},
Expand Down Expand Up @@ -109,16 +109,16 @@ func getRegisteredRegionalResources() []AwsResources {
}
}

func toAwsResourcesPointer(resources []AwsResources) []*AwsResources {
var awsResourcePointers []*AwsResources
func toAwsResourcesPointer(resources []AwsResource) []*AwsResource {
var awsResourcePointers []*AwsResource
for i := range resources {
awsResourcePointers = append(awsResourcePointers, &resources[i])
}

return awsResourcePointers
}

func initRegisteredResources(resources []*AwsResources, session *session.Session, region string) []*AwsResources {
func initRegisteredResources(resources []*AwsResource, session *session.Session, region string) []*AwsResource {
for _, resource := range resources {
(*resource).Init(session)

Expand All @@ -129,7 +129,7 @@ func initRegisteredResources(resources []*AwsResources, session *session.Session
return resources
}

func setRegionForRegionalResource(regionResource *AwsResources, region string) {
func setRegionForRegionalResource(regionResource *AwsResource, region string) {
// Use reflection to set the Region field if the resource type has it
resourceValue := reflect.ValueOf(*regionResource) // Dereference the pointer
resourceValue = resourceValue.Elem() // Get the underlying value
Expand Down
Loading

0 comments on commit 5c9b6fc

Please sign in to comment.