From 958a23b852cd9548d3fda68054dec4c7f5560637 Mon Sep 17 00:00:00 2001 From: James Kwon Date: Wed, 20 Mar 2024 08:08:39 -0400 Subject: [PATCH 1/3] timing out nuke though cli --- README.md | 189 ++++++++++++++++------------- aws/aws.go | 10 +- aws/query.go | 4 +- aws/query_test.go | 5 +- aws/resource.go | 3 + aws/resource_registry.go | 1 - aws/resources/base_resource.go | 56 ++++++--- aws/resources/msk_cluster.go | 22 +--- aws/resources/msk_cluster_test.go | 4 +- aws/resources/msk_cluster_types.go | 24 ++-- aws/resources/s3.go | 116 +++++++++--------- aws/resources/s3_test.go | 26 ++-- aws/resources/s3_types.go | 5 + commands/cli.go | 27 ++++- config/config.go | 25 ++++ config/config_test.go | 166 ++++++++++++------------- report/report.go | 5 + util/error.go | 31 ++++- 18 files changed, 433 insertions(+), 286 deletions(-) diff --git a/README.md b/README.md index 24a92183..e4d2c0c7 100644 --- a/README.md +++ b/README.md @@ -311,6 +311,15 @@ Dry run mode is only available within: - `cloud-nuke aws` +### With Timeout +If you want to set up a timeout option for resources, limiting their execution to a specified duration for nuking, use the +`--timeout` flag: + +```shell +cloud-nuke aws --resource-type s3 --timeout 10m +``` +This will attempt to nuke the specified resources within a 10-minute timeframe. + ### Using cloud-nuke as a library You can import cloud-nuke into other projects and use it as a library for programmatically inspecting and counting @@ -342,6 +351,9 @@ func main() { // an optional start time- can pass null if the filter is not required includeAfter := time.Now().AddDate(-1, 0, 0) + // an optional execution timeout duration + timeout := time.Duration(10 * time.Second) + // Any custom settings you want myCustomConfig := &aws.Config{} myCustomConfig.WithMaxRetries(3) @@ -363,6 +375,7 @@ func main() { &excludeAfter, &includeAfter, false, + &timeout, ) if err != nil { fmt.Println(err) @@ -486,6 +499,18 @@ s3: exclude: tag: 'foo' ``` +#### Timeout +You have the flexibility to set individual timeout options for specific resources. The execution will pause until the designated timeout is reached for each resource. +```yaml +s3: + timeout: 10m + + ........ + +s3: + timeout: 5s + +``` By default, it will use the exclusion default tag: `cloud-nuke-excluded` to exclude resources. _Note: it doesn't support including resources by tags._ @@ -495,88 +520,88 @@ _Note: it doesn't support including resources by tags._ To find out what we options are supported in the config file today, consult this table. Resource types at the top level of the file that are supported are listed here. -| resource type | config key | names_regex | time | tags | -|-----------------------------|------------------------------|----------------------------------------|--------------------------------------|-------| -| acm | ACM | ✅ (Domain Name) | ✅ (Created Time) | ❌ | -| acmpca | ACMPCA | ❌ | ✅ (LastStateChange or Created Time) | ❌ | -| ami | AMI | ✅ (Image Name) | ✅ (Creation Time) | ❌ | -| apigateway | APIGateway | ✅ (API Name) | ✅ (Created Time) | ❌ | -| apigatewayv2 | APIGatewayV2 | ✅ (API Name) | ✅ (Created Time) | ❌ | -| accessanalyzer | AccessAnalyzer | ✅ (Analyzer Name) | ✅ (Created Time) | ❌ | -| asg | AutoScalingGroup | ✅ (ASG Name) | ✅ (Created Time) | ✅ | -| backup-vault | BackupVault | ✅ (Backup Vault Name) | ✅ (Created Time) | ❌ | -| cloudwatch-alarm | CloudWatchAlarm | ✅ (Alarm Name) | ✅ (AlarmConfigurationUpdated Time) | ❌ | -| cloudwatch-dashboard | CloudWatchDashboard | ✅ (Dashboard Name) | ✅ (LastModified Time) | ❌ | -| cloudwatch-loggroup | CloudWatchLogGroup | ✅ (Log Group Name) | ✅ (Creation Time) | ❌ | -| cloudtrail | CloudtrailTrail | ✅ (Trail Name) | ❌ | ❌ | -| codedeploy-application | CodeDeployApplications | ✅ (Application Name) | ✅ (Creation Time) | ❌ | -| config-recorders | ConfigServiceRecorder | ✅ (Recorder Name) | ❌ | ❌ | -| config-rules | ConfigServiceRule | ✅ (Rule Name) | ❌ | ❌ | -| dynamodb | DynamoDB | ✅ (Table Name) | ✅ (Creation Time) | ❌ | -| ebs | EBSVolume | ✅ (Volume Name) | ✅ (Creation Time) | ✅ | -| elastic-beanstalk | ElasticBeanstalk | ✅ (Application Name) | ✅ (Creation Time) | ❌ | -| ec2 | EC2 | ✅ (Instance Name) | ✅ (Launch Time) | ✅ | -| 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) | ✅ | -| ecr | ECRRepository | ✅ (Repository Name) | ✅ (Creation Time) | ❌ | -| ecscluster | ECSCluster | ✅ (Cluster Name) | ❌ | ❌ | -| ecsserv | ECSService | ✅ (Service Name) | ✅ (Creation Time) | ❌ | -| ekscluster | EKSCluster | ✅ (Cluster Name) | ✅ (Creation Time) | ✅ | -| elb | ELBv1 | ✅ (Load Balancer Name) | ✅ (Created Time) | ❌ | -| elbv2 | ELBv2 | ✅ (Load Balancer Name) | ✅ (Created Time) | ❌ | -| efs | ElasticFileSystem | ✅ (File System Name) | ✅ (Creation Time) | ❌ | -| eip | ElasticIP | ✅ (Elastic IP Allocation Name) | ✅ (First Seen Tag Time) | ✅ | -| elasticache | Elasticache | ✅ (Cluster ID & Replication Group ID) | ✅ (Creation Time) | ❌ | -| elasticacheparametergroups | ElasticacheParameterGroups | ✅ (Parameter Group Name) | ❌ | ❌ | -| elasticachesubnetgroups | ElasticacheSubnetGroups | ✅ (Subnet Group Name) | ❌ | ❌ | -| guardduty | GuardDuty | ❌ | ✅ (Created Time) | ❌ | -| iam-group | IAMGroups | ✅ (Group Name) | ✅ (Creation Time) | ❌ | -| iam-policy | IAMPolicies | ✅ (Policy Name) | ✅ (Creation Time) | ❌ | -| iam-role | IAMRoles | ✅ (Role Name) | ✅ (Creation Time) | ❌ | -| iam-service-linked-role | IAMServiceLinkedRoles | ✅ (Service Linked Role Name) | ✅ (Creation Time) | ❌ | -| iam | IAMUsers | ✅ (User Name) | ✅ (Creation Time) | ✅ | -| kmscustomerkeys | KMSCustomerKeys | ✅ (Key Name) | ✅ (Creation Time) | ❌ | -| kinesis-stream | KinesisStream | ✅ (Stream Name) | ❌ | ❌ | -| lambda | LambdaFunction | ✅ (Function Name) | ✅ (Last Modified Time) | ❌ | -| lc | LaunchConfiguration | ✅ (Launch Configuration Name) | ✅ (Created Time) | ❌ | -| lt | LaunchTemplate | ✅ (Launch Template Name) | ✅ (Created Time) | ❌ | -| macie-member | MacieMember | ❌ | ✅ (Creation Time) | ❌ | -| msk-cluster | MSKCluster | ✅ (Cluster Name) | ✅ (Creation Time) | ❌ | -| nat-gateway | NatGateway | ✅ (EC2 Name Tag) | ✅ (Creation Time) | ✅ | -| oidcprovider | OIDCProvider | ✅ (Provider URL) | ✅ (Creation Time) | ❌ | -| opensearchdomain | OpenSearchDomain | ✅ (Domain Name) | ✅ (First Seen Tag Time) | ❌ | -| redshift | Redshift | ✅ (Cluster Identifier) | ✅ (Creation Time) | ❌ | -| rds-cluster | DBClusters | ✅ (DB Cluster Identifier ) | ✅ (Creation Time) | ✅ | -| rds | DBInstances | ✅ (DB Name) | ✅ (Creation Time) | ✅ | -| rds-parameter-group | RdsParameterGroup | ✅ (Group Name) | ❌ | ❌ | -| rds-subnet-group | DBSubnetGroups | ✅ (DB Subnet Group Name) | ❌ | ❌ | -| s3 | s3 | ✅ (Bucket Name) | ✅ (Creation Time) | ✅ | -| s3-ap | s3AccessPoint | ✅ (Access point Name) | ❌ | ❌ | -| s3-olap | S3ObjectLambdaAccessPoint | ✅ (Object Lambda Access point Name) | ❌ | ❌ | -| s3-mrap | S3MultiRegionAccessPoint | ✅ (Multi region Access point Name) | ✅ (Creation Time) | ❌ | -| ses-configuration-set | SesConfigurationset | ✅ (Configuration set name) | ❌ | ❌ | -| ses-email-template | SesEmailTemplates | ✅ (Template Name) | ✅ (Creation Time) | ❌ | -| ses-identity | SesIdentity | ✅ (Identity -Mail/Domain) | ❌ | ❌ | -| ses-receipt-rule-set | SesReceiptRuleSet | ✅ (Receipt Rule Set Name) | ✅ (Creation Time) | ❌ | -| ses-receipt-filter | SesReceiptFilter | ✅ (Receipt Filter Name) | ❌ | ❌ | -| snstopic | SNS | ✅ (Topic Name) | ✅ (First Seen Tag Time) | ❌ | -| sqs | SQS | ✅ (Queue Name) | ✅ (Creation Time) | ❌ | -| sagemaker-notebook-smni | SageMakerNotebook | ✅ (Notebook Instnace Name) | ✅ (Creation Time) | ❌ | -| secretsmanager | SecretsManagerSecrets | ✅ (Secret Name) | ✅ (Last Accessed or Creation Time) | ❌ | -| security-hub | SecurityHub | ❌ | ✅ (Created Time) | ❌ | -| snap | Snapshots | ❌ | ✅ (Creation Time) | ✅ | -| transit-gateway | TransitGateway | ❌ | ✅ (Creation Time) | ❌ | -| 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) | ❌ | ❌ | +| resource type | config key | names_regex | time | tags | timeout | +|-----------------------------|------------------------------|----------------------------------------|--------------------------------------|-------|---------| +| acm | ACM | ✅ (Domain Name) | ✅ (Created Time) | ❌ | ❌ | +| acmpca | ACMPCA | ❌ | ✅ (LastStateChange or Created Time) | ❌ | ❌ | +| ami | AMI | ✅ (Image Name) | ✅ (Creation Time) | ❌ | ❌ | +| apigateway | APIGateway | ✅ (API Name) | ✅ (Created Time) | ❌ | ❌ | +| apigatewayv2 | APIGatewayV2 | ✅ (API Name) | ✅ (Created Time) | ❌ | ❌ | +| accessanalyzer | AccessAnalyzer | ✅ (Analyzer Name) | ✅ (Created Time) | ❌ | ❌ | +| asg | AutoScalingGroup | ✅ (ASG Name) | ✅ (Created Time) | ✅ | ❌ | +| backup-vault | BackupVault | ✅ (Backup Vault Name) | ✅ (Created Time) | ❌ | ❌ | +| cloudwatch-alarm | CloudWatchAlarm | ✅ (Alarm Name) | ✅ (AlarmConfigurationUpdated Time) | ❌ | ❌ | +| cloudwatch-dashboard | CloudWatchDashboard | ✅ (Dashboard Name) | ✅ (LastModified Time) | ❌ | ❌ | +| cloudwatch-loggroup | CloudWatchLogGroup | ✅ (Log Group Name) | ✅ (Creation Time) | ❌ | ❌ | +| cloudtrail | CloudtrailTrail | ✅ (Trail Name) | ❌ | ❌ | ❌ | +| codedeploy-application | CodeDeployApplications | ✅ (Application Name) | ✅ (Creation Time) | ❌ | ❌ | +| config-recorders | ConfigServiceRecorder | ✅ (Recorder Name) | ❌ | ❌ | ❌ | +| config-rules | ConfigServiceRule | ✅ (Rule Name) | ❌ | ❌ | ❌ | +| dynamodb | DynamoDB | ✅ (Table Name) | ✅ (Creation Time) | ❌ | ❌ | +| ebs | EBSVolume | ✅ (Volume Name) | ✅ (Creation Time) | ✅ | ❌ | +| elastic-beanstalk | ElasticBeanstalk | ✅ (Application Name) | ✅ (Creation Time) | ❌ | ❌ | +| ec2 | EC2 | ✅ (Instance Name) | ✅ (Launch Time) | ✅ | ❌ | +| 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) | ✅ | ❌ | +| ecr | ECRRepository | ✅ (Repository Name) | ✅ (Creation Time) | ❌ | ❌ | +| ecscluster | ECSCluster | ✅ (Cluster Name) | ❌ | ❌ | ❌ | +| ecsserv | ECSService | ✅ (Service Name) | ✅ (Creation Time) | ❌ | ❌ | +| ekscluster | EKSCluster | ✅ (Cluster Name) | ✅ (Creation Time) | ✅ | ❌ | +| elb | ELBv1 | ✅ (Load Balancer Name) | ✅ (Created Time) | ❌ | ❌ | +| elbv2 | ELBv2 | ✅ (Load Balancer Name) | ✅ (Created Time) | ❌ | ❌ | +| efs | ElasticFileSystem | ✅ (File System Name) | ✅ (Creation Time) | ❌ | ❌ | +| eip | ElasticIP | ✅ (Elastic IP Allocation Name) | ✅ (First Seen Tag Time) | ✅ | ❌ | +| elasticache | Elasticache | ✅ (Cluster ID & Replication Group ID) | ✅ (Creation Time) | ❌ | ❌ | +| elasticacheparametergroups | ElasticacheParameterGroups | ✅ (Parameter Group Name) | ❌ | ❌ | ❌ | +| elasticachesubnetgroups | ElasticacheSubnetGroups | ✅ (Subnet Group Name) | ❌ | ❌ | ❌ | +| guardduty | GuardDuty | ❌ | ✅ (Created Time) | ❌ | ❌ | +| iam-group | IAMGroups | ✅ (Group Name) | ✅ (Creation Time) | ❌ | ❌ | +| iam-policy | IAMPolicies | ✅ (Policy Name) | ✅ (Creation Time) | ❌ | ❌ | +| iam-role | IAMRoles | ✅ (Role Name) | ✅ (Creation Time) | ❌ | ❌ | +| iam-service-linked-role | IAMServiceLinkedRoles | ✅ (Service Linked Role Name) | ✅ (Creation Time) | ❌ | ❌ | +| iam | IAMUsers | ✅ (User Name) | ✅ (Creation Time) | ✅ | ❌ | +| kmscustomerkeys | KMSCustomerKeys | ✅ (Key Name) | ✅ (Creation Time) | ❌ | ❌ | +| kinesis-stream | KinesisStream | ✅ (Stream Name) | ❌ | ❌ | ❌ | +| lambda | LambdaFunction | ✅ (Function Name) | ✅ (Last Modified Time) | ❌ | ❌ | +| lc | LaunchConfiguration | ✅ (Launch Configuration Name) | ✅ (Created Time) | ❌ | ❌ | +| lt | LaunchTemplate | ✅ (Launch Template Name) | ✅ (Created Time) | ❌ | ❌ | +| macie-member | MacieMember | ❌ | ✅ (Creation Time) | ❌ | ❌ | +| msk-cluster | MskCluster | ✅ (Cluster Name) | ✅ (Creation Time) | ❌ | ❌ | +| nat-gateway | NatGateway | ✅ (EC2 Name Tag) | ✅ (Creation Time) | ✅ | ❌ | +| oidcprovider | OIDCProvider | ✅ (Provider URL) | ✅ (Creation Time) | ❌ | ❌ | +| opensearchdomain | OpenSearchDomain | ✅ (Domain Name) | ✅ (First Seen Tag Time) | ❌ | ❌ | +| redshift | Redshift | ✅ (Cluster Identifier) | ✅ (Creation Time) | ❌ | ❌ | +| rds-cluster | DBClusters | ✅ (DB Cluster Identifier ) | ✅ (Creation Time) | ✅ | ❌ | +| rds | DBInstances | ✅ (DB Name) | ✅ (Creation Time) | ✅ | ❌ | +| rds-parameter-group | RdsParameterGroup | ✅ (Group Name) | ❌ | ❌ | ❌ | +| rds-subnet-group | DBSubnetGroups | ✅ (DB Subnet Group Name) | ❌ | ❌ | ❌ | +| s3 | s3 | ✅ (Bucket Name) | ✅ (Creation Time) | ✅ | ✅ | +| s3-ap | s3AccessPoint | ✅ (Access point Name) | ❌ | ❌ | ❌ | +| s3-olap | S3ObjectLambdaAccessPoint | ✅ (Object Lambda Access point Name) | ❌ | ❌ | ❌ | +| s3-mrap | S3MultiRegionAccessPoint | ✅ (Multi region Access point Name) | ✅ (Creation Time) | ❌ | ❌ | +| ses-configuration-set | SesConfigurationset | ✅ (Configuration set name) | ❌ | ❌ | ❌ | +| ses-email-template | SesEmailTemplates | ✅ (Template Name) | ✅ (Creation Time) | ❌ | ❌ | +| ses-identity | SesIdentity | ✅ (Identity -Mail/Domain) | ❌ | ❌ | ❌ | +| ses-receipt-rule-set | SesReceiptRuleSet | ✅ (Receipt Rule Set Name) | ✅ (Creation Time) | ❌ | ❌ | +| ses-receipt-filter | SesReceiptFilter | ✅ (Receipt Filter Name) | ❌ | ❌ | ❌ | +| snstopic | SNS | ✅ (Topic Name) | ✅ (First Seen Tag Time) | ❌ | ❌ | +| sqs | SQS | ✅ (Queue Name) | ✅ (Creation Time) | ❌ | ❌ | +| sagemaker-notebook-smni | SageMakerNotebook | ✅ (Notebook Instnace Name) | ✅ (Creation Time) | ❌ | ❌ | +| secretsmanager | SecretsManagerSecrets | ✅ (Secret Name) | ✅ (Last Accessed or Creation Time) | ❌ | ❌ | +| security-hub | SecurityHub | ❌ | ✅ (Created Time) | ❌ | ❌ | +| snap | Snapshots | ❌ | ✅ (Creation Time) | ✅ | ❌ | +| transit-gateway | TransitGateway | ❌ | ✅ (Creation Time) | ❌ | ❌ | +| 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/aws.go b/aws/aws.go index 44209b6c..765494e2 100644 --- a/aws/aws.go +++ b/aws/aws.go @@ -21,9 +21,10 @@ import ( // GetAllResources - Lists all aws resources func GetAllResources(c context.Context, query *Query, configObj config.Config) (*AwsAccountResources, error) { - configObj.AddExcludeAfterTime(query.ExcludeAfter) configObj.AddIncludeAfterTime(query.IncludeAfter) + configObj.AddTimeout(query.Timeout) + configObj.KMSCustomerKeys.IncludeUnaliasedKeys = query.ListUnaliasedKMSKeys account := AwsAccountResources{ Resources: make(map[string]AwsResources), @@ -42,11 +43,18 @@ func GetAllResources(c context.Context, query *Query, configObj config.Config) ( registeredResources := GetAndInitRegisteredResources(cloudNukeSession, region) for _, resource := range registeredResources { if IsNukeable((*resource).ResourceName(), query.ResourceTypes) { + + // PrepareContext sets up the resource context for execution, utilizing the context 'c' and the resource individual configuration. + // This function should be called after configuring the timeout to ensure proper execution context. + resourceConfig := (*resource).GetAndSetResourceConfig(configObj) + (*resource).PrepareContext(c, resourceConfig) + spinner.UpdateText( fmt.Sprintf("Searching %s resources in %s", (*resource).ResourceName(), region)) start := time.Now() identifiers, err := (*resource).GetAndSetIdentifiers(c, configObj) if err != nil { + logging.Errorf("Unable to retrieve %v, %v", (*resource).ResourceName(), err) ge := report.GeneralError{ Error: err, Description: fmt.Sprintf("Unable to retrieve %s", (*resource).ResourceName()), diff --git a/aws/query.go b/aws/query.go index f622215f..accbe91b 100644 --- a/aws/query.go +++ b/aws/query.go @@ -13,10 +13,11 @@ type Query struct { ExcludeAfter *time.Time IncludeAfter *time.Time ListUnaliasedKMSKeys bool + Timeout *time.Duration } // 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) { +func NewQuery(regions, excludeRegions, resourceTypes, excludeResourceTypes []string, excludeAfter, includeAfter *time.Time, listUnaliasedKMSKeys bool, timeout *time.Duration) (*Query, error) { q := &Query{ Regions: regions, ExcludeRegions: excludeRegions, @@ -25,6 +26,7 @@ func NewQuery(regions, excludeRegions, resourceTypes, excludeResourceTypes []str ExcludeAfter: excludeAfter, IncludeAfter: includeAfter, ListUnaliasedKMSKeys: listUnaliasedKMSKeys, + Timeout: timeout, } validationErr := q.Validate() diff --git a/aws/query_test.go b/aws/query_test.go index fb988dc3..49c3620a 100644 --- a/aws/query_test.go +++ b/aws/query_test.go @@ -1,10 +1,11 @@ package aws import ( - "github.com/aws/aws-sdk-go/aws" "testing" "time" + "github.com/aws/aws-sdk-go/aws" + "github.com/gruntwork-io/cloud-nuke/telemetry" "github.com/stretchr/testify/require" @@ -43,7 +44,7 @@ func TestNewQueryAcceptsValidExcludeAfterEntries(t *testing.T) { for _, tc := range testCases { t.Run(tc.Name, func(t *testing.T) { - _, err := NewQuery(tc.Regions, tc.ExcludeRegions, tc.ResourceTypes, tc.ExcludeResourceTypes, tc.ExcludeAfter, tc.IncludeAfter, false) + _, err := NewQuery(tc.Regions, tc.ExcludeRegions, tc.ResourceTypes, tc.ExcludeResourceTypes, tc.ExcludeAfter, tc.IncludeAfter, false, nil) require.NoError(t, err) }) } diff --git a/aws/resource.go b/aws/resource.go index 3d3a9cd4..0317daac 100644 --- a/aws/resource.go +++ b/aws/resource.go @@ -17,6 +17,9 @@ type AwsResource interface { Nuke(identifiers []string) error GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error) IsNukable(string) (bool, error) + + PrepareContext(context.Context, config.ResourceType) error + GetAndSetResourceConfig(config.Config) config.ResourceType } // AwsResources is a struct to hold multiple instances of AwsResource. diff --git a/aws/resource_registry.go b/aws/resource_registry.go index c2c2e98e..80351c20 100644 --- a/aws/resource_registry.go +++ b/aws/resource_registry.go @@ -94,7 +94,6 @@ func getRegisteredRegionalResources() []AwsResource { &resources.LaunchConfigs{}, &resources.LaunchTemplates{}, &resources.MacieMember{}, - &resources.MSKCluster{}, &resources.NatGateways{}, &resources.OpenSearchDomains{}, &resources.DBInstances{}, diff --git a/aws/resources/base_resource.go b/aws/resources/base_resource.go index a028abb4..2eb05602 100644 --- a/aws/resources/base_resource.go +++ b/aws/resources/base_resource.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "time" "github.com/aws/aws-sdk-go/aws/session" "github.com/gruntwork-io/cloud-nuke/config" @@ -16,52 +17,75 @@ import ( type BaseAwsResource struct { // A key-value of identifiers and nukable status Nukables map[string]error + Timeout time.Duration + Context context.Context + cancel context.CancelFunc } -func (umpl *BaseAwsResource) Init(_ *session.Session) { - umpl.Nukables = make(map[string]error) +func (br *BaseAwsResource) Init(_ *session.Session) { + br.Nukables = make(map[string]error) } -func (umpl *BaseAwsResource) ResourceName() string { +func (br *BaseAwsResource) ResourceName() string { return "not implemented: ResourceName" } -func (umpl *BaseAwsResource) ResourceIdentifiers() []string { +func (br *BaseAwsResource) ResourceIdentifiers() []string { return nil } -func (umpl *BaseAwsResource) MaxBatchSize() int { +func (br *BaseAwsResource) MaxBatchSize() int { return 0 } -func (umpl *BaseAwsResource) Nuke(_ []string) error { +func (br *BaseAwsResource) Nuke(_ []string) error { return errors.New("not implemented: Nuke") } -func (umpl *BaseAwsResource) GetAndSetIdentifiers(_ context.Context, _ config.Config) ([]string, error) { +func (br *BaseAwsResource) GetAndSetIdentifiers(_ context.Context, _ config.Config) ([]string, error) { return nil, errors.New("not implemented: GetAndSetIdentifiers") } -func (umpl *BaseAwsResource) GetNukableStatus(identifier string) (error, bool) { - val, ok := umpl.Nukables[identifier] +func (br *BaseAwsResource) GetNukableStatus(identifier string) (error, bool) { + val, ok := br.Nukables[identifier] return val, ok } -func (umpl *BaseAwsResource) SetNukableStatus(identifier string, err error) { - umpl.Nukables[identifier] = err +func (br *BaseAwsResource) SetNukableStatus(identifier string, err error) { + br.Nukables[identifier] = err +} +func (br *BaseAwsResource) GetAndSetResourceConfig(_ config.Config) config.ResourceType { + return config.ResourceType{ + Timeout: "", + } +} + +func (br *BaseAwsResource) PrepareContext(parentContext context.Context, resourceConfig config.ResourceType) error { + if resourceConfig.Timeout == "" { + br.Context = parentContext + return nil + } + + duration, err := time.ParseDuration(resourceConfig.Timeout) + if err != nil { + return err + } + + br.Context, _ = context.WithTimeout(parentContext, duration) + return nil } // VerifyNukablePermissions performs nukable permission verification for each ID. For each ID, the function is // executed, and the result (error or success) is recorded using the SetNukableStatus method, indicating whether // the specified action is nukable -func (umpl *BaseAwsResource) VerifyNukablePermissions(ids []*string, nukableCheckfn func(id *string) error) { +func (br *BaseAwsResource) VerifyNukablePermissions(ids []*string, nukableCheckfn func(id *string) error) { for _, id := range ids { // skip if the id is already exists - if _, ok := umpl.GetNukableStatus(*id); ok { + if _, ok := br.GetNukableStatus(*id); ok { continue } err := nukableCheckfn(id) - umpl.SetNukableStatus(*id, util.TransformAWSError(err)) + br.SetNukableStatus(*id, util.TransformAWSError(err)) } } -func (umpl *BaseAwsResource) IsNukable(identifier string) (bool, error) { - err, ok := umpl.Nukables[identifier] +func (br *BaseAwsResource) IsNukable(identifier string) (bool, error) { + err, ok := br.Nukables[identifier] if !ok { return false, fmt.Errorf("-") } diff --git a/aws/resources/msk_cluster.go b/aws/resources/msk_cluster.go index 9f807e2b..97db1f55 100644 --- a/aws/resources/msk_cluster.go +++ b/aws/resources/msk_cluster.go @@ -2,15 +2,13 @@ package resources import ( "context" - - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/kafka" "github.com/gruntwork-io/cloud-nuke/config" "github.com/gruntwork-io/cloud-nuke/logging" "github.com/gruntwork-io/cloud-nuke/report" ) -func (m *MSKCluster) getAll(c context.Context, configObj config.Config) ([]*string, error) { +func (m MSKCluster) getAll(c context.Context, configObj config.Config) ([]*string, error) { var clusterIDs []*string err := m.Client.ListClustersV2Pages(&kafka.ListClustersV2Input{}, func(page *kafka.ListClustersV2Output, lastPage bool) bool { @@ -28,7 +26,7 @@ func (m *MSKCluster) getAll(c context.Context, configObj config.Config) ([]*stri return clusterIDs, nil } -func (m *MSKCluster) shouldInclude(cluster *kafka.Cluster, configObj config.Config) bool { +func (m MSKCluster) shouldInclude(cluster *kafka.Cluster, configObj config.Config) bool { if *cluster.State == kafka.ClusterStateDeleting { return false } @@ -39,26 +37,16 @@ func (m *MSKCluster) shouldInclude(cluster *kafka.Cluster, configObj config.Conf return false } - // if cluster is in maintenance, skip it as it will only throw an error when attempting to delete it - // BadRequestException: You can't delete cluster in MAINTENANCE state. - if *cluster.State == kafka.ClusterStateMaintenance { - return false - } - return configObj.MSKCluster.ShouldInclude(config.ResourceValue{ Name: cluster.ClusterName, Time: cluster.CreationTime, }) } -func (m *MSKCluster) nukeAll(identifiers []*string) error { - if len(identifiers) == 0 { - return nil - } - +func (m MSKCluster) nukeAll(identifiers []string) error { for _, clusterArn := range identifiers { _, err := m.Client.DeleteCluster(&kafka.DeleteClusterInput{ - ClusterArn: clusterArn, + ClusterArn: &clusterArn, }) if err != nil { logging.Errorf("[Failed] %s", err) @@ -66,7 +54,7 @@ func (m *MSKCluster) nukeAll(identifiers []*string) error { // Record status of this resource e := report.Entry{ - Identifier: aws.StringValue(clusterArn), + Identifier: clusterArn, ResourceType: "MSKCluster", Error: err, } diff --git a/aws/resources/msk_cluster_test.go b/aws/resources/msk_cluster_test.go index 98cb6d8c..6d6ccfa4 100644 --- a/aws/resources/msk_cluster_test.go +++ b/aws/resources/msk_cluster_test.go @@ -194,7 +194,7 @@ func TestShouldIncludeMSKCluster(t *testing.T) { IncludeRule: config.FilterRule{ NamesRegExp: []config.Expression{ { - RE: *regexp.MustCompile("^test-cluster"), + RE: *regexp.MustCompile("test-cluster"), }, }, }, @@ -226,7 +226,7 @@ func TestNukeMSKCluster(t *testing.T) { Client: &mockMskClient, } - err := msk.Nuke([]string{}) + err := msk.Nuke(nil, []string{}) if err != nil { t.Fatalf("Unable to nuke MSK Clusters: %v", err) } diff --git a/aws/resources/msk_cluster_types.go b/aws/resources/msk_cluster_types.go index 574fc2d1..81e32731 100644 --- a/aws/resources/msk_cluster_types.go +++ b/aws/resources/msk_cluster_types.go @@ -19,40 +19,40 @@ type MSKCluster struct { ClusterArns []string } -func (m *MSKCluster) Init(session *session.Session) { - m.Client = kafka.New(session) +func (msk MSKCluster) Init(session *session.Session) { + msk.Client = kafka.New(session) } // ResourceName - the simple name of the aws resource -func (m *MSKCluster) ResourceName() string { +func (msk MSKCluster) ResourceName() string { return "msk-cluster" } // ResourceIdentifiers - The instance ids of the AWS Managed Streaming for Kafka clusters -func (m *MSKCluster) ResourceIdentifiers() []string { - return m.ClusterArns +func (msk MSKCluster) ResourceIdentifiers() []string { + return msk.ClusterArns } -func (m *MSKCluster) MaxBatchSize() int { +func (msk MSKCluster) MaxBatchSize() int { // Tentative batch size to ensure AWS doesn't throttle. Note that nat gateway does not support bulk delete, so // we will be deleting this many in parallel using go routines. We conservatively pick 10 here, both to limit // overloading the runtime and to avoid AWS throttling with many API calls. return 10 } -func (m *MSKCluster) GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error) { - identifiers, err := m.getAll(c, configObj) +func (msk MSKCluster) GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error) { + identifiers, err := msk.getAll(c, configObj) if err != nil { return nil, err } - m.ClusterArns = awsgo.StringValueSlice(identifiers) - return m.ClusterArns, nil + msk.ClusterArns = awsgo.StringValueSlice(identifiers) + return msk.ClusterArns, nil } // Nuke - nuke 'em all!!! -func (m *MSKCluster) Nuke(identifiers []string) error { - if err := m.nukeAll(awsgo.StringSlice(identifiers)); err != nil { +func (msk MSKCluster) Nuke(_ *session.Session, identifiers []string) error { + if err := msk.nukeAll(identifiers); err != nil { return errors.WithStackTrace(err) } diff --git a/aws/resources/s3.go b/aws/resources/s3.go index 29be1eb9..9be0b6fe 100644 --- a/aws/resources/s3.go +++ b/aws/resources/s3.go @@ -3,13 +3,15 @@ package resources import ( "context" "fmt" - "github.com/gruntwork-io/cloud-nuke/telemetry" - "github.com/gruntwork-io/cloud-nuke/util" - commonTelemetry "github.com/gruntwork-io/go-commons/telemetry" "math" "sync" "time" + "github.com/gruntwork-io/cloud-nuke/report" + "github.com/gruntwork-io/cloud-nuke/telemetry" + "github.com/gruntwork-io/cloud-nuke/util" + commonTelemetry "github.com/gruntwork-io/go-commons/telemetry" + "github.com/hashicorp/go-multierror" "github.com/aws/aws-sdk-go/aws" @@ -19,7 +21,6 @@ import ( "github.com/gruntwork-io/cloud-nuke/config" "github.com/gruntwork-io/cloud-nuke/logging" - "github.com/gruntwork-io/cloud-nuke/report" ) const AwsResourceExclusionTagKey = "cloud-nuke-excluded" @@ -30,7 +31,7 @@ func (sb S3Buckets) getS3BucketRegion(bucketName string) (string, error) { Bucket: aws.String(bucketName), } - result, err := sb.Client.GetBucketLocation(input) + result, err := sb.Client.GetBucketLocationWithContext(sb.Context, input) if err != nil { return "", err } @@ -51,7 +52,7 @@ func (bucket *S3Buckets) getS3BucketTags(bucketName string) (map[string]string, // Please note that svc argument should be created from a session object which is // in the same region as the bucket or GetBucketTagging will fail. - result, err := bucket.Client.GetBucketTagging(input) + result, err := bucket.Client.GetBucketTaggingWithContext(bucket.Context, input) if err != nil { if aerr, ok := err.(awserr.Error); ok { switch aerr.Code() { @@ -77,8 +78,7 @@ type S3Bucket struct { // getAllS3Buckets returns a map of per region AWS S3 buckets which were created before excludeAfter func (sb S3Buckets) getAll(c context.Context, configObj config.Config) ([]*string, error) { - input := &s3.ListBucketsInput{} - output, err := sb.Client.ListBuckets(input) + output, err := sb.Client.ListBucketsWithContext(sb.Context, &s3.ListBucketsInput{}) if err != nil { return nil, errors.WithStackTrace(err) } @@ -204,7 +204,8 @@ func (sb S3Buckets) emptyBucket(bucketName *string, isVersioned bool) error { // Handle versioned buckets. if isVersioned { - err := sb.Client.ListObjectVersionsPages( + err := sb.Client.ListObjectVersionsPagesWithContext( + sb.Context, &s3.ListObjectVersionsInput{ Bucket: bucketName, MaxKeys: aws.Int64(int64(sb.MaxBatchSize())), @@ -240,7 +241,8 @@ func (sb S3Buckets) emptyBucket(bucketName *string, isVersioned bool) error { } // Handle non versioned buckets. - err := sb.Client.ListObjectsV2Pages( + err := sb.Client.ListObjectsV2PagesWithContext( + sb.Context, &s3.ListObjectsV2Input{ Bucket: bucketName, MaxKeys: aws.Int64(int64(sb.MaxBatchSize())), @@ -280,7 +282,8 @@ func (sb S3Buckets) deleteObjects(bucketName *string, objects []*s3.Object) erro Key: obj.Key, }) } - _, err := sb.Client.DeleteObjects( + _, err := sb.Client.DeleteObjectsWithContext( + sb.Context, &s3.DeleteObjectsInput{ Bucket: bucketName, Delete: &s3.Delete{ @@ -306,7 +309,8 @@ func (sb S3Buckets) deleteObjectVersions(bucketName *string, objectVersions []*s VersionId: obj.VersionId, }) } - _, err := sb.Client.DeleteObjects( + _, err := sb.Client.DeleteObjectsWithContext( + sb.Context, &s3.DeleteObjectsInput{ Bucket: bucketName, Delete: &s3.Delete{ @@ -332,7 +336,8 @@ func (sb S3Buckets) deleteDeletionMarkers(bucketName *string, objectDelMarkers [ VersionId: obj.VersionId, }) } - _, err := sb.Client.DeleteObjects( + _, err := sb.Client.DeleteObjectsWithContext( + sb.Context, &s3.DeleteObjectsInput{ Bucket: bucketName, Delete: &s3.Delete{ @@ -346,7 +351,7 @@ func (sb S3Buckets) deleteDeletionMarkers(bucketName *string, objectDelMarkers [ // nukeAllS3BucketObjects batch deletes all objects in an S3 bucket func (sb S3Buckets) nukeAllS3BucketObjects(bucketName *string) error { - versioningResult, err := sb.Client.GetBucketVersioning(&s3.GetBucketVersioningInput{ + versioningResult, err := sb.Client.GetBucketVersioningWithContext(sb.Context, &s3.GetBucketVersioningInput{ Bucket: bucketName, }) if err != nil { @@ -369,7 +374,8 @@ func (sb S3Buckets) nukeAllS3BucketObjects(bucketName *string) error { // nukeEmptyS3Bucket deletes an empty S3 bucket func (sb S3Buckets) nukeEmptyS3Bucket(bucketName *string, verifyBucketDeletion bool) error { - _, err := sb.Client.DeleteBucket(&s3.DeleteBucketInput{ + + _, err := sb.Client.DeleteBucketWithContext(sb.Context, &s3.DeleteBucketInput{ Bucket: bucketName, }) if err != nil { @@ -385,7 +391,7 @@ func (sb S3Buckets) nukeEmptyS3Bucket(bucketName *string, verifyBucketDeletion b const maxRetries = 3 for i := 0; i < maxRetries; i++ { logging.Debugf("Waiting until bucket (%s) deletion is propagated (attempt %d / %d)", aws.StringValue(bucketName), i+1, maxRetries) - err = sb.Client.WaitUntilBucketNotExists(&s3.HeadBucketInput{ + err = sb.Client.WaitUntilBucketNotExistsWithContext(sb.Context, &s3.HeadBucketInput{ Bucket: bucketName, }) // Exit early if no error @@ -400,15 +406,36 @@ func (sb S3Buckets) nukeEmptyS3Bucket(bucketName *string, verifyBucketDeletion b } func (sb S3Buckets) nukeS3BucketPolicy(bucketName *string) error { - _, err := sb.Client.DeleteBucketPolicy(&s3.DeleteBucketPolicyInput{ - Bucket: aws.String(*bucketName), - }) + _, err := sb.Client.DeleteBucketPolicyWithContext( + sb.Context, + &s3.DeleteBucketPolicyInput{ + Bucket: aws.String(*bucketName), + }) return err } +func (sb S3Buckets) nukeBucket(bucketName *string) error { + verifyBucketDeletion := true + + err := sb.nukeAllS3BucketObjects(bucketName) + if err != nil { + return err + } + + err = sb.nukeS3BucketPolicy(bucketName) + if err != nil { + return err + } + + err = sb.nukeEmptyS3Bucket(bucketName, verifyBucketDeletion) + if err != nil { + return err + } + return nil +} + // nukeAllS3Buckets deletes all S3 buckets passed as input func (sb S3Buckets) nukeAll(bucketNames []*string) (delCount int, err error) { - verifyBucketDeletion := true if len(bucketNames) == 0 { logging.Debugf("No S3 Buckets to nuke in region %s", sb.Region) @@ -420,36 +447,23 @@ func (sb S3Buckets) nukeAll(bucketNames []*string) (delCount int, err error) { logging.Debugf("Deleting - %d S3 Buckets in region %s", totalCount, sb.Region) multiErr := new(multierror.Error) + + var deleted []*string for bucketIndex := 0; bucketIndex < totalCount; bucketIndex++ { bucketName := bucketNames[bucketIndex] logging.Debugf("Deleting - %d/%d - Bucket: %s", bucketIndex+1, totalCount, *bucketName) - err = sb.nukeAllS3BucketObjects(bucketName) - if err != nil { - logging.Debugf("[Failed] - %d/%d - Bucket: %s - object deletion error - %s", bucketIndex+1, totalCount, *bucketName, err) - telemetry.TrackEvent(commonTelemetry.EventContext{ - EventName: "Error Nuking S3 Bucket Objects", - }, map[string]interface{}{ - "region": sb.Region, - }) - multierror.Append(multiErr, err) - continue - } + err := sb.nukeBucket(bucketName) - err = sb.nukeS3BucketPolicy(bucketName) - if err != nil { - logging.Debugf("[Failed] - %d/%d - Bucket: %s - bucket policy cleanup error - %s", bucketIndex+1, totalCount, *bucketName, err) - telemetry.TrackEvent(commonTelemetry.EventContext{ - EventName: "Error Nuking S3 Bucket Polikcy", - }, map[string]interface{}{ - "region": sb.Region, - }) - multierror.Append(multiErr, err) - continue + // Record status of this resource + e := report.Entry{ + Identifier: aws.StringValue(bucketName), + ResourceType: "S3 Bucket", + Error: err, } + report.Record(e) - err = sb.nukeEmptyS3Bucket(bucketName, verifyBucketDeletion) if err != nil { logging.Debugf("[Failed] - %d/%d - Bucket: %s - bucket deletion error - %s", bucketIndex+1, totalCount, *bucketName, err) telemetry.TrackEvent(commonTelemetry.EventContext{ @@ -457,21 +471,15 @@ func (sb S3Buckets) nukeAll(bucketNames []*string) (delCount int, err error) { }, map[string]interface{}{ "region": sb.Region, }) - multierror.Append(multiErr, err) - continue - } - - // Record status of this resource - e := report.Entry{ - Identifier: aws.StringValue(bucketName), - ResourceType: "S3 Bucket", - Error: multiErr.ErrorOrNil(), + } else { + deleted = append(deleted, bucketName) + logging.Debugf("[OK] - %d/%d - Bucket: %s - deleted", bucketIndex+1, totalCount, *bucketName) + delCount++ } - report.Record(e) - logging.Debugf("[OK] - %d/%d - Bucket: %s - deleted", bucketIndex+1, totalCount, *bucketName) - delCount++ } + logging.Debugf("[OK] - %d Bucket(s) deleted in %s", len(deleted), sb.Region) + return delCount, multiErr.ErrorOrNil() } diff --git a/aws/resources/s3_test.go b/aws/resources/s3_test.go index 973f3b64..627b5c90 100644 --- a/aws/resources/s3_test.go +++ b/aws/resources/s3_test.go @@ -2,15 +2,17 @@ package resources import ( "context" + "regexp" + "testing" + "time" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/service/s3" "github.com/aws/aws-sdk-go/service/s3/s3iface" "github.com/gruntwork-io/cloud-nuke/config" "github.com/gruntwork-io/cloud-nuke/telemetry" "github.com/stretchr/testify/require" - "regexp" - "testing" - "time" ) type mockedS3Buckets struct { @@ -25,40 +27,40 @@ type mockedS3Buckets struct { DeleteBucketOutput s3.DeleteBucketOutput } -func (m mockedS3Buckets) ListBuckets(*s3.ListBucketsInput) (*s3.ListBucketsOutput, error) { +func (m mockedS3Buckets) ListBucketsWithContext(aws.Context, *s3.ListBucketsInput, ...request.Option) (*s3.ListBucketsOutput, error) { return &m.ListBucketsOutput, nil } -func (m mockedS3Buckets) GetBucketLocation(*s3.GetBucketLocationInput) (*s3.GetBucketLocationOutput, error) { +func (m mockedS3Buckets) GetBucketLocationWithContext(aws.Context, *s3.GetBucketLocationInput, ...request.Option) (*s3.GetBucketLocationOutput, error) { return &m.GetBucketLocationOutput, nil } -func (m mockedS3Buckets) GetBucketTagging(*s3.GetBucketTaggingInput) (*s3.GetBucketTaggingOutput, error) { +func (m mockedS3Buckets) GetBucketTaggingWithContext(aws.Context, *s3.GetBucketTaggingInput, ...request.Option) (*s3.GetBucketTaggingOutput, error) { return &m.GetBucketTaggingOutput, nil } -func (m mockedS3Buckets) WaitUntilBucketNotExists(*s3.HeadBucketInput) error { +func (m mockedS3Buckets) WaitUntilBucketNotExistsWithContext(aws.Context, *s3.HeadBucketInput, ...request.WaiterOption) error { return nil } -func (m mockedS3Buckets) GetBucketVersioning(*s3.GetBucketVersioningInput) (*s3.GetBucketVersioningOutput, error) { +func (m mockedS3Buckets) GetBucketVersioningWithContext(aws.Context, *s3.GetBucketVersioningInput, ...request.Option) (*s3.GetBucketVersioningOutput, error) { return &m.GetBucketVersioningOutput, nil } -func (m mockedS3Buckets) ListObjectVersionsPages(input *s3.ListObjectVersionsInput, fn func(*s3.ListObjectVersionsOutput, bool) bool) error { +func (m mockedS3Buckets) ListObjectVersionsPagesWithContext(_ aws.Context, _ *s3.ListObjectVersionsInput, fn func(*s3.ListObjectVersionsOutput, bool) bool, _ ...request.Option) error { fn(&m.ListObjectVersionsPagesOutput, true) return nil } -func (m mockedS3Buckets) DeleteObjects(*s3.DeleteObjectsInput) (*s3.DeleteObjectsOutput, error) { +func (m mockedS3Buckets) DeleteObjectsWithContext(aws.Context, *s3.DeleteObjectsInput, ...request.Option) (*s3.DeleteObjectsOutput, error) { return &m.DeleteObjectsOutput, nil } -func (m mockedS3Buckets) DeleteBucketPolicy(*s3.DeleteBucketPolicyInput) (*s3.DeleteBucketPolicyOutput, error) { +func (m mockedS3Buckets) DeleteBucketPolicyWithContext(aws.Context, *s3.DeleteBucketPolicyInput, ...request.Option) (*s3.DeleteBucketPolicyOutput, error) { return &m.DeleteBucketPolicyOutput, nil } -func (m mockedS3Buckets) DeleteBucket(*s3.DeleteBucketInput) (*s3.DeleteBucketOutput, error) { +func (m mockedS3Buckets) DeleteBucketWithContext(aws.Context, *s3.DeleteBucketInput, ...request.Option) (*s3.DeleteBucketOutput, error) { return &m.DeleteBucketOutput, nil } diff --git a/aws/resources/s3_types.go b/aws/resources/s3_types.go index 08ebc032..2734302a 100644 --- a/aws/resources/s3_types.go +++ b/aws/resources/s3_types.go @@ -52,6 +52,11 @@ func (bucket *S3Buckets) ResourceIdentifiers() []string { return bucket.Names } +// To get the resource configuration +func (bucket *S3Buckets) GetAndSetResourceConfig(configObj config.Config) config.ResourceType { + return configObj.S3 +} + func (bucket *S3Buckets) GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error) { identifiers, err := bucket.getAll(c, configObj) if err != nil { diff --git a/commands/cli.go b/commands/cli.go index 9ab54b32..89d40422 100644 --- a/commands/cli.go +++ b/commands/cli.go @@ -2,11 +2,12 @@ package commands import ( "fmt" - "github.com/gruntwork-io/cloud-nuke/aws" - "github.com/gruntwork-io/cloud-nuke/aws/resources" "os" "time" + "github.com/gruntwork-io/cloud-nuke/aws" + "github.com/gruntwork-io/cloud-nuke/aws/resources" + "github.com/gruntwork-io/cloud-nuke/telemetry" commonTelemetry "github.com/gruntwork-io/go-commons/telemetry" @@ -95,6 +96,10 @@ func CreateCli(version string) *cli.App { Name: "config", Usage: "YAML file specifying matching rules.", }, + &cli.StringFlag{ + Name: "timeout", + Usage: "Resource execution timeout.", + }, }, }, { Name: "defaults-aws", @@ -193,6 +198,18 @@ func parseDurationParam(paramValue string) (*time.Time, error) { return &excludeAfter, nil } +func parseTimeoutDurationParam(paramValue string) (*time.Duration, error) { + if paramValue == "0s" || paramValue == "" { + return nil, nil + } + + duration, err := time.ParseDuration(paramValue) + if err != nil { + return nil, errors.WithStackTrace(err) + } + return &duration, nil +} + func awsNuke(c *cli.Context) error { telemetry.TrackEvent(commonTelemetry.EventContext{ EventName: "Start aws", @@ -518,6 +535,11 @@ func handleGetResources(c *cli.Context, configObj config.Config, includeUnaliase return nil, nil, errors.WithStackTrace(err) } + timeout, err := parseTimeoutDurationParam(c.String("timeout")) + if err != nil { + return nil, nil, errors.WithStackTrace(err) + } + query, err := aws.NewQuery( c.StringSlice("region"), c.StringSlice("exclude-region"), @@ -526,6 +548,7 @@ func handleGetResources(c *cli.Context, configObj config.Config, includeUnaliase excludeAfter, includeAfter, includeUnaliasedKmsKeys, + timeout, ) if err != nil { return nil, nil, aws.QueryCreationError{Underlying: err} diff --git a/config/config.go b/config/config.go index 1d62b801..9ea7c3e7 100644 --- a/config/config.go +++ b/config/config.go @@ -118,6 +118,24 @@ func (c *Config) addTimeAfterFilter(timeFilter *time.Time, fieldName string) { filterRule.TimeAfter = timeFilter } } +func (c *Config) addTimeOut(timeout *time.Duration, fieldName string) { + // Do nothing if the time filter is nil or 0s + if timeout == nil || *timeout <= 0 { + return + } + + v := reflect.ValueOf(c).Elem() + for i := 0; i < v.NumField(); i++ { + field := v.Field(i) + if field.Kind() != reflect.Struct { + continue + } + + timeoutField := field.FieldByName(fieldName) + timeoutVal := timeoutField.Addr().Interface().(*string) + *timeoutVal = timeout.String() + } +} func (c *Config) AddIncludeAfterTime(includeAfter *time.Time) { // include after filter has been applied to all resources via `newer-than` flag, we are @@ -131,6 +149,12 @@ func (c *Config) AddExcludeAfterTime(excludeAfter *time.Time) { c.addTimeAfterFilter(excludeAfter, "ExcludeRule") } +func (c *Config) AddTimeout(timeout *time.Duration) { + // include after filter has been applied to all resources via `newer-than` flag, we are + // setting this rule across all resource types. + c.addTimeOut(timeout, "Timeout") +} + type KMSCustomerKeyResourceType struct { IncludeUnaliasedKeys bool `yaml:"include_unaliased_keys"` ResourceType `yaml:",inline"` @@ -139,6 +163,7 @@ type KMSCustomerKeyResourceType struct { type ResourceType struct { IncludeRule FilterRule `yaml:"include"` ExcludeRule FilterRule `yaml:"exclude"` + Timeout string `yaml:"timeout"` } type FilterRule struct { diff --git a/config/config_test.go b/config/config_test.go index d1b9c284..614ef476 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -13,89 +13,89 @@ import ( func emptyConfig() *Config { return &Config{ - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - KMSCustomerKeyResourceType{false, ResourceType{FilterRule{}, FilterRule{}}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, - ResourceType{FilterRule{}, FilterRule{}}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + KMSCustomerKeyResourceType{false, ResourceType{FilterRule{}, FilterRule{}, ""}}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, } } diff --git a/report/report.go b/report/report.go index e775784b..3401a50d 100644 --- a/report/report.go +++ b/report/report.go @@ -2,6 +2,8 @@ package report import ( "sync" + + "github.com/gruntwork-io/cloud-nuke/util" ) /** @@ -35,6 +37,9 @@ func ResetErrors() { func Record(e Entry) { defer m.Unlock() m.Lock() + + // Transform the aws error into custom error format. + e.Error = util.TransformAWSError(e.Error) records[e.Identifier] = e } diff --git a/util/error.go b/util/error.go index f9791c57..4c361550 100644 --- a/util/error.go +++ b/util/error.go @@ -2,13 +2,18 @@ package util import ( "errors" + "fmt" + "time" + "github.com/aws/aws-sdk-go/aws/awserr" ) var ErrInSufficientPermission = errors.New("error:INSUFFICIENT_PERMISSION") var ErrDifferentOwner = errors.New("error:DIFFERENT_OWNER") +var ErrContextExecutionTimeout = errors.New("error:EXECUTION_TIMEOUT") const AWsUnauthorizedError string = "UnauthorizedOperation" +const AwsDryrunSuccess string = "Request would have succeeded, but DryRun flag is set." // TransformAWSError // this function is used to handle AWS errors and mapping them to a custom error message @@ -19,5 +24,29 @@ func TransformAWSError(err error) error { if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == AWsUnauthorizedError { return ErrInSufficientPermission } - return nil + if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "RequestCanceled" { + return ErrContextExecutionTimeout + } + + if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "DryRunOperation" && awsErr.Message() == AwsDryrunSuccess { + return nil + } + return err + +} + +type ResourceExecutionTimeout struct { + Timeout time.Duration +} + +func (err ResourceExecutionTimeout) Error() string { + return fmt.Sprintf("execution timed out after: %v", err.Timeout) +} + +// Check if the error is due to context deadline exceeded +func CheckDeadlineExceeded(err error) bool { + if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "RequestCanceled" { + return true + } + return false } From c001261a167be5c5e9a667722af704c79c4ce0dc Mon Sep 17 00:00:00 2001 From: James Kwon Date: Wed, 20 Mar 2024 08:16:38 -0400 Subject: [PATCH 2/3] rebase --- aws/resource_registry.go | 1 + aws/resources/msk_cluster.go | 22 +++++++++++++++++----- aws/resources/msk_cluster_test.go | 4 ++-- aws/resources/msk_cluster_types.go | 24 ++++++++++++------------ 4 files changed, 32 insertions(+), 19 deletions(-) diff --git a/aws/resource_registry.go b/aws/resource_registry.go index 80351c20..c2c2e98e 100644 --- a/aws/resource_registry.go +++ b/aws/resource_registry.go @@ -94,6 +94,7 @@ func getRegisteredRegionalResources() []AwsResource { &resources.LaunchConfigs{}, &resources.LaunchTemplates{}, &resources.MacieMember{}, + &resources.MSKCluster{}, &resources.NatGateways{}, &resources.OpenSearchDomains{}, &resources.DBInstances{}, diff --git a/aws/resources/msk_cluster.go b/aws/resources/msk_cluster.go index 97db1f55..9f807e2b 100644 --- a/aws/resources/msk_cluster.go +++ b/aws/resources/msk_cluster.go @@ -2,13 +2,15 @@ package resources import ( "context" + + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/kafka" "github.com/gruntwork-io/cloud-nuke/config" "github.com/gruntwork-io/cloud-nuke/logging" "github.com/gruntwork-io/cloud-nuke/report" ) -func (m MSKCluster) getAll(c context.Context, configObj config.Config) ([]*string, error) { +func (m *MSKCluster) getAll(c context.Context, configObj config.Config) ([]*string, error) { var clusterIDs []*string err := m.Client.ListClustersV2Pages(&kafka.ListClustersV2Input{}, func(page *kafka.ListClustersV2Output, lastPage bool) bool { @@ -26,7 +28,7 @@ func (m MSKCluster) getAll(c context.Context, configObj config.Config) ([]*strin return clusterIDs, nil } -func (m MSKCluster) shouldInclude(cluster *kafka.Cluster, configObj config.Config) bool { +func (m *MSKCluster) shouldInclude(cluster *kafka.Cluster, configObj config.Config) bool { if *cluster.State == kafka.ClusterStateDeleting { return false } @@ -37,16 +39,26 @@ func (m MSKCluster) shouldInclude(cluster *kafka.Cluster, configObj config.Confi return false } + // if cluster is in maintenance, skip it as it will only throw an error when attempting to delete it + // BadRequestException: You can't delete cluster in MAINTENANCE state. + if *cluster.State == kafka.ClusterStateMaintenance { + return false + } + return configObj.MSKCluster.ShouldInclude(config.ResourceValue{ Name: cluster.ClusterName, Time: cluster.CreationTime, }) } -func (m MSKCluster) nukeAll(identifiers []string) error { +func (m *MSKCluster) nukeAll(identifiers []*string) error { + if len(identifiers) == 0 { + return nil + } + for _, clusterArn := range identifiers { _, err := m.Client.DeleteCluster(&kafka.DeleteClusterInput{ - ClusterArn: &clusterArn, + ClusterArn: clusterArn, }) if err != nil { logging.Errorf("[Failed] %s", err) @@ -54,7 +66,7 @@ func (m MSKCluster) nukeAll(identifiers []string) error { // Record status of this resource e := report.Entry{ - Identifier: clusterArn, + Identifier: aws.StringValue(clusterArn), ResourceType: "MSKCluster", Error: err, } diff --git a/aws/resources/msk_cluster_test.go b/aws/resources/msk_cluster_test.go index 6d6ccfa4..98cb6d8c 100644 --- a/aws/resources/msk_cluster_test.go +++ b/aws/resources/msk_cluster_test.go @@ -194,7 +194,7 @@ func TestShouldIncludeMSKCluster(t *testing.T) { IncludeRule: config.FilterRule{ NamesRegExp: []config.Expression{ { - RE: *regexp.MustCompile("test-cluster"), + RE: *regexp.MustCompile("^test-cluster"), }, }, }, @@ -226,7 +226,7 @@ func TestNukeMSKCluster(t *testing.T) { Client: &mockMskClient, } - err := msk.Nuke(nil, []string{}) + err := msk.Nuke([]string{}) if err != nil { t.Fatalf("Unable to nuke MSK Clusters: %v", err) } diff --git a/aws/resources/msk_cluster_types.go b/aws/resources/msk_cluster_types.go index 81e32731..574fc2d1 100644 --- a/aws/resources/msk_cluster_types.go +++ b/aws/resources/msk_cluster_types.go @@ -19,40 +19,40 @@ type MSKCluster struct { ClusterArns []string } -func (msk MSKCluster) Init(session *session.Session) { - msk.Client = kafka.New(session) +func (m *MSKCluster) Init(session *session.Session) { + m.Client = kafka.New(session) } // ResourceName - the simple name of the aws resource -func (msk MSKCluster) ResourceName() string { +func (m *MSKCluster) ResourceName() string { return "msk-cluster" } // ResourceIdentifiers - The instance ids of the AWS Managed Streaming for Kafka clusters -func (msk MSKCluster) ResourceIdentifiers() []string { - return msk.ClusterArns +func (m *MSKCluster) ResourceIdentifiers() []string { + return m.ClusterArns } -func (msk MSKCluster) MaxBatchSize() int { +func (m *MSKCluster) MaxBatchSize() int { // Tentative batch size to ensure AWS doesn't throttle. Note that nat gateway does not support bulk delete, so // we will be deleting this many in parallel using go routines. We conservatively pick 10 here, both to limit // overloading the runtime and to avoid AWS throttling with many API calls. return 10 } -func (msk MSKCluster) GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error) { - identifiers, err := msk.getAll(c, configObj) +func (m *MSKCluster) GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error) { + identifiers, err := m.getAll(c, configObj) if err != nil { return nil, err } - msk.ClusterArns = awsgo.StringValueSlice(identifiers) - return msk.ClusterArns, nil + m.ClusterArns = awsgo.StringValueSlice(identifiers) + return m.ClusterArns, nil } // Nuke - nuke 'em all!!! -func (msk MSKCluster) Nuke(_ *session.Session, identifiers []string) error { - if err := msk.nukeAll(identifiers); err != nil { +func (m *MSKCluster) Nuke(identifiers []string) error { + if err := m.nukeAll(awsgo.StringSlice(identifiers)); err != nil { return errors.WithStackTrace(err) } From f6f83c9247b42e468c94a1a34355d4e2481b8515 Mon Sep 17 00:00:00 2001 From: James Kwon Date: Sat, 23 Mar 2024 07:46:06 -0400 Subject: [PATCH 3/3] timing out nuke though cli --- README.md | 2 +- aws/aws.go | 5 ++++- go.mod | 2 +- util/error.go | 12 ++---------- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index e4d2c0c7..1cd9f04b 100644 --- a/README.md +++ b/README.md @@ -571,7 +571,7 @@ of the file that are supported are listed here. | lc | LaunchConfiguration | ✅ (Launch Configuration Name) | ✅ (Created Time) | ❌ | ❌ | | lt | LaunchTemplate | ✅ (Launch Template Name) | ✅ (Created Time) | ❌ | ❌ | | macie-member | MacieMember | ❌ | ✅ (Creation Time) | ❌ | ❌ | -| msk-cluster | MskCluster | ✅ (Cluster Name) | ✅ (Creation Time) | ❌ | ❌ | +| msk-cluster | MSKCluster | ✅ (Cluster Name) | ✅ (Creation Time) | ❌ | ❌ | | nat-gateway | NatGateway | ✅ (EC2 Name Tag) | ✅ (Creation Time) | ✅ | ❌ | | oidcprovider | OIDCProvider | ✅ (Provider URL) | ✅ (Creation Time) | ❌ | ❌ | | opensearchdomain | OpenSearchDomain | ✅ (Domain Name) | ✅ (First Seen Tag Time) | ❌ | ❌ | diff --git a/aws/aws.go b/aws/aws.go index 765494e2..f46ea99f 100644 --- a/aws/aws.go +++ b/aws/aws.go @@ -47,7 +47,10 @@ func GetAllResources(c context.Context, query *Query, configObj config.Config) ( // PrepareContext sets up the resource context for execution, utilizing the context 'c' and the resource individual configuration. // This function should be called after configuring the timeout to ensure proper execution context. resourceConfig := (*resource).GetAndSetResourceConfig(configObj) - (*resource).PrepareContext(c, resourceConfig) + err := (*resource).PrepareContext(c, resourceConfig) + if err != nil { + return nil, err + } spinner.UpdateText( fmt.Sprintf("Searching %s resources in %s", (*resource).ResourceName(), region)) diff --git a/go.mod b/go.mod index 5c2fb5a9..24263cbc 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/aws/aws-sdk-go v1.49.13 github.com/aws/aws-sdk-go-v2 v1.24.0 github.com/charmbracelet/lipgloss v0.6.0 + github.com/go-errors/errors v1.4.2 github.com/gruntwork-io/go-commons v0.17.0 github.com/gruntwork-io/gruntwork-cli v0.7.0 github.com/hashicorp/go-multierror v1.1.1 @@ -23,7 +24,6 @@ require ( 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 - github.com/go-errors/errors v1.4.2 // indirect github.com/google/uuid v1.2.0 // indirect github.com/gookit/color v1.5.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect diff --git a/util/error.go b/util/error.go index 4c361550..8a41df96 100644 --- a/util/error.go +++ b/util/error.go @@ -13,7 +13,7 @@ var ErrDifferentOwner = errors.New("error:DIFFERENT_OWNER") var ErrContextExecutionTimeout = errors.New("error:EXECUTION_TIMEOUT") const AWsUnauthorizedError string = "UnauthorizedOperation" -const AwsDryrunSuccess string = "Request would have succeeded, but DryRun flag is set." +const AwsDryRunSuccess string = "Request would have succeeded, but DryRun flag is set." // TransformAWSError // this function is used to handle AWS errors and mapping them to a custom error message @@ -28,7 +28,7 @@ func TransformAWSError(err error) error { return ErrContextExecutionTimeout } - if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "DryRunOperation" && awsErr.Message() == AwsDryrunSuccess { + if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "DryRunOperation" && awsErr.Message() == AwsDryRunSuccess { return nil } return err @@ -42,11 +42,3 @@ type ResourceExecutionTimeout struct { func (err ResourceExecutionTimeout) Error() string { return fmt.Sprintf("execution timed out after: %v", err.Timeout) } - -// Check if the error is due to context deadline exceeded -func CheckDeadlineExceeded(err error) bool { - if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "RequestCanceled" { - return true - } - return false -}