diff --git a/Makefile b/Makefile index 81e33d52..e4024d23 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,9 @@ docs-serve: docs-seed: cp README.md docs/index.md +build: + go build -ldflags '-s -w -extldflags="-static"' -o bin/aws-nuke main.go + generate: go generate ./... @@ -14,4 +17,4 @@ test: go test ./... test-integration: - go test ./... -tags=integration \ No newline at end of file + go test ./... -tags=integration diff --git a/docs/features/name-expansion.md b/docs/features/name-expansion.md new file mode 100644 index 00000000..1e142357 --- /dev/null +++ b/docs/features/name-expansion.md @@ -0,0 +1,51 @@ +# Name Expansion + +This allows you to use wildcards in the resource names to match multiple resources. This is primarily useful when you +want to target a group of resource type for either inclusion or exclusion. + +Resource Name expansion is valid for use in the following areas: + +!!! warning +This feature is currently **NOT** supported in filters. + +- cli includes/excludes +- config resource types includes/excludes +- account resource types includes/excludes + +## Examples + +### CLI + +```console +aws-nuke run --config config.yaml --include "Cognito*" +``` + +This can also be used with `resource-types` subcommand to see what resource types are available, and you can specify +multiple wildcard arguments. + +```console +aws-nuke resource-types "Cognito*" "IAM*" +``` + +### Config + +```yaml +resource-types: + includes: + - "Cognito*" + excludes: + - "OpsWorks*" +``` + +### Account Config + +```yaml +accounts: + '012345678912': + resource-types: + includes: + - "Cognito*" + excludes: + - "OpsWorks*" +``` + diff --git a/docs/features/overview.md b/docs/features/overview.md index 95aa6a6d..305fb16e 100644 --- a/docs/features/overview.md +++ b/docs/features/overview.md @@ -6,6 +6,8 @@ Some of the new features include: - [Run Against All Enabled Regions](enabled-regions.md) - [Bypass Alias Check - Allow the skip of an alias on an account](bypass-alias-check.md) - [Signed Binaries](signed-binaries.md) +- [Filter Groups (Experimental)](filter-groups.md) +- [Name Expansion](name-expansion.md) Additionally, there are a few new sub commands to the tool to help with setup and debugging purposes: diff --git a/docs/index.md b/docs/index.md index 9f6499bc..6930e3b1 100644 --- a/docs/index.md +++ b/docs/index.md @@ -18,6 +18,7 @@ This is not a comprehensive list, but here are some of the highlights: * New Feature: [Run Against All Enabled Regions](features/enabled-regions.md) * New Feature: [Bypass Alias Check - Allow the skip of an alias on an account](features/bypass-alias-check.md) * New Feature: [Filter Groups (Experimental)](features/filter-groups.md) +* New Feature: [Name Expansion](features/name-expansion.md) * Breaking Change: `root` command no longer triggers the run, must use subcommand `run` (alias: `nuke`) * Completely rewrote the core of the tool as a dedicated library [libnuke](https://github.com/ekristen/libnuke) * This library has over 95% test coverage which makes iteration and new features easier to implement. diff --git a/docs/installation.md b/docs/installation.md index 529583ea..e2aefb2e 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -3,7 +3,7 @@ Preferred installation order is the following: 1. [GitHub Release](#github-releases-preferred) -2. [ekristen's homebrew tap](#ekristens-homebrew-tap-macoslinux) +2. [Homebrew Tap](#ekristens-homebrew-tap-macoslinux) 3. [Homebrew Core](#homebrew-core-macoslinux) Docker images are also available via the GitHub Container Registry. @@ -13,18 +13,24 @@ Docker images are also available via the GitHub Container Registry. !!! success - "Recommended" This supports all operating systems and most architectures. -You can download pre-compiled binaries from the [releases](https://github.com/ekristen/aws-nuke/releases) page. +You can download pre-compiled binaries from the [releases](https://github.com/ekristen/aws-nuke/releases) page, or you can use my tool +[distillery](https://github.com/ekristen/distillery) to download and install the latest version. -## ekristen's Homebrew Tap (MacOS/Linux) +```console +dist install ekristen/aws-nuke +``` + +## Homebrew Tap (macOS) !!! info - I control this tap, and it sources the binaries directly from the GitHub releases. However, it only supports MacOS. + I control this tap, and it sources the binaries directly from the GitHub releases. However, it only supports MacOS + and it tends to lag a bit behind. ```console brew install ekristen/tap/aws-nuke ``` -## Homebrew Core (MacOS/Linux) +## Homebrew Core (macOS/Linux) !!! note I do not control the Homebrew Core formula, so it may not be up to date. Additionally, it is not compiled with @@ -50,3 +56,18 @@ To compile **aws-nuke** from source you need a working [Golang](https://golang.o goreleaser build --clean --snapshot --single-target ``` +## Verifying Binaries + +All the binaries are signed with [cosign](https://github.com/sigstore/cosign) and are signed with keyless signatures. +You can verify the build using the public transparency log and the cosign binary. + +**Note:** swap out `VERSION` with `vX.Y.Z`. + +```console +cosign verify-blob \ + --signature https://github.com/ekristen/aws-nuke/releases/download/VERSION/checksums.txt.sig \ + --certificate https://github.com/ekristen/aws-nuke/releases/download/VERSION/checksums.txt.pem \ + --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \ + --certificate-identity "https://github.com/ekristen/aws-nuke/.github/workflows/goreleaser.yml@refs/tags/VERSION" \ + https://github.com/ekristen/aws-nuke/releases/download/VERSION/checksums.txt +``` diff --git a/docs/resources/cloud-formation-stack.md b/docs/resources/cloud-formation-stack.md index e7402ac1..0ec3fd84 100644 --- a/docs/resources/cloud-formation-stack.md +++ b/docs/resources/cloud-formation-stack.md @@ -11,8 +11,16 @@ generated: true CloudFormationStack ``` +## Properties +- `CreationTime`: No Description +- `LastUpdatedTime`: No Description +- `Name`: No Description +- `Status`: No Description +- `tag::`: This resource has tags with property `Tags`. These are key/value pairs that are + added as their own property with the prefix of `tag:` (e.g. [tag:example: "value"]) + !!! note - Using Properties Properties are what [Filters](../config-filtering.md) are written against in your configuration. You use the property names to write filters for what you want to **keep** and omit from the nuke process. @@ -28,6 +36,7 @@ The string value is always what is used in the output of the log format when a r ## Settings - `DisableDeletionProtection` +- `CreateRoleToDeleteStack` ### DisableDeletionProtection @@ -40,3 +49,14 @@ The string value is always what is used in the output of the log format when a r DisableDeletionProtection ``` + +### CreateRoleToDeleteStack + +!!! note + There is currently no description for this setting. Often times settings are fairly self-explanatory. However, we + are working on adding descriptions for all settings. + +```text +CreateRoleToDeleteStack +``` + diff --git a/docs/resources/cloud-watch-alarm.md b/docs/resources/cloud-watch-alarm.md index faaadf34..64b58c02 100644 --- a/docs/resources/cloud-watch-alarm.md +++ b/docs/resources/cloud-watch-alarm.md @@ -11,8 +11,14 @@ generated: true CloudWatchAlarm ``` +## Properties +- `Name`: No Description +- `Type`: No Description +- `tag::`: This resource has tags with property `Tags`. These are key/value pairs that are + added as their own property with the prefix of `tag:` (e.g. [tag:example: "value"]) + !!! note - Using Properties Properties are what [Filters](../config-filtering.md) are written against in your configuration. You use the property names to write filters for what you want to **keep** and omit from the nuke process. diff --git a/docs/resources/cloud-watch-events-target.md b/docs/resources/cloud-watch-events-target.md index ed49fc9f..397d1dc2 100644 --- a/docs/resources/cloud-watch-events-target.md +++ b/docs/resources/cloud-watch-events-target.md @@ -11,8 +11,13 @@ generated: true CloudWatchEventsTarget ``` +## Properties +- `BusName`: The name of the event bus the rule applies to +- `Name`: The name of the rule +- `TargetID`: The ID of the target for the rule + !!! note - Using Properties Properties are what [Filters](../config-filtering.md) are written against in your configuration. You use the property names to write filters for what you want to **keep** and omit from the nuke process. diff --git a/docs/resources/cloud-watch-logs-log-group.md b/docs/resources/cloud-watch-logs-log-group.md index a9d24a0d..a105b73e 100644 --- a/docs/resources/cloud-watch-logs-log-group.md +++ b/docs/resources/cloud-watch-logs-log-group.md @@ -11,8 +11,17 @@ generated: true CloudWatchLogsLogGroup ``` +## Properties +- `CreatedTime`: The creation time of the log group in unix timestamp format +- `CreationTime`: The creation time of the log group in RFC3339 format +- `LastEvent`: The last event time of the log group in RFC3339 format +- `Name`: The name of the log group +- `RetentionInDays`: The number of days to retain log events in the log group +- `tag::`: This resource has tags with property `Tags`. These are key/value pairs that are + added as their own property with the prefix of `tag:` (e.g. [tag:example: "value"]) + !!! note - Using Properties Properties are what [Filters](../config-filtering.md) are written against in your configuration. You use the property names to write filters for what you want to **keep** and omit from the nuke process. diff --git a/docs/resources/ec2-tgw.md b/docs/resources/ec2-tgw.md index 45df81e7..459cb40d 100644 --- a/docs/resources/ec2-tgw.md +++ b/docs/resources/ec2-tgw.md @@ -11,8 +11,15 @@ generated: true EC2TGW ``` +## Properties +- `ID`: The ID of the transit gateway. +- `OwnerId`: The ID of the AWS account that owns the transit gateway. +- `State`: The state of the transit gateway. +- `tag::`: This resource has tags with property `Tags`. These are key/value pairs that are + added as their own property with the prefix of `tag:` (e.g. [tag:example: "value"]) + !!! note - Using Properties Properties are what [Filters](../config-filtering.md) are written against in your configuration. You use the property names to write filters for what you want to **keep** and omit from the nuke process. diff --git a/docs/resources/kms-alias.md b/docs/resources/kms-alias.md index aebe4705..c59fff3e 100644 --- a/docs/resources/kms-alias.md +++ b/docs/resources/kms-alias.md @@ -14,7 +14,11 @@ KMSAlias ## Properties -- `Name`: No Description +- `CreationDate`: The creation date of the KMS alias +- `Name`: The name of the KMS alias +- `TargetKeyID`: The KMS Key ID that the alias points to +- `tag::`: This resource has tags with property `Tags`. These are key/value pairs that are + added as their own property with the prefix of `tag:` (e.g. [tag:example: "value"]) !!! note - Using Properties Properties are what [Filters](../config-filtering.md) are written against in your configuration. You use the property diff --git a/docs/resources/kms-key.md b/docs/resources/kms-key.md index b2cb038b..1801030f 100644 --- a/docs/resources/kms-key.md +++ b/docs/resources/kms-key.md @@ -33,6 +33,19 @@ the filter. The string value is always what is used in the output of the log format when a resource is identified. +## Settings + +- `IgnoreErrors` + + +### IgnoreErrors + +KMS keys can be often in a state that can't be deleted if the KMS policy had been malformed. Give option to igore error in order to not fail the overall nuke. + +```text +IgnoreErrors +``` + ### DependsOn !!! important - Experimental Feature diff --git a/docs/resources/neptune-cluster.md b/docs/resources/neptune-cluster.md index f68c11ae..334f4bb1 100644 --- a/docs/resources/neptune-cluster.md +++ b/docs/resources/neptune-cluster.md @@ -15,6 +15,9 @@ NeptuneCluster - `ID`: No Description +- `Status`: No Description +- `tag::`: This resource has tags with property `Tags`. These are key/value pairs that are + added as their own property with the prefix of `tag:` (e.g. [tag:example: "value"]) !!! note - Using Properties Properties are what [Filters](../config-filtering.md) are written against in your configuration. You use the property @@ -28,3 +31,26 @@ the filter. The string value is always what is used in the output of the log format when a resource is identified. +## Settings + +- `DisableDeletionProtection` + + +### DisableDeletionProtection + +!!! note + There is currently no description for this setting. Often times settings are fairly self-explanatory. However, we + are working on adding descriptions for all settings. + +```text +DisableDeletionProtection +``` + +### DependsOn + +!!! important - Experimental Feature + This resource depends on a resource using the experimental feature. This means that the resource will + only be deleted if all the resources of a particular type are deleted first or reach a terminal state. + +- [NeptuneInstance](./neptune-instance.md) + diff --git a/docs/resources/neptune-instance.md b/docs/resources/neptune-instance.md index 89855d98..d8177a5b 100644 --- a/docs/resources/neptune-instance.md +++ b/docs/resources/neptune-instance.md @@ -14,8 +14,10 @@ NeptuneInstance ## Properties +- `ClusterID`: No Description - `ID`: No Description - `Name`: No Description +- `Status`: No Description - `tag::`: This resource has tags with property `Tags`. These are key/value pairs that are added as their own property with the prefix of `tag:` (e.g. [tag:example: "value"]) @@ -31,3 +33,30 @@ the filter. The string value is always what is used in the output of the log format when a resource is identified. +## Settings + +- `DisableClusterDeletionProtection` +- `DisableDeletionProtection` + + +### DisableClusterDeletionProtection + +!!! note + There is currently no description for this setting. Often times settings are fairly self-explanatory. However, we + are working on adding descriptions for all settings. + +```text +DisableClusterDeletionProtection +``` + + +### DisableDeletionProtection + +!!! note + There is currently no description for this setting. Often times settings are fairly self-explanatory. However, we + are working on adding descriptions for all settings. + +```text +DisableDeletionProtection +``` + diff --git a/docs/resources/neptune-snapshot.md b/docs/resources/neptune-snapshot.md index a1c02696..4a938c37 100644 --- a/docs/resources/neptune-snapshot.md +++ b/docs/resources/neptune-snapshot.md @@ -14,7 +14,9 @@ NeptuneSnapshot ## Properties +- `CreateTime`: No Description - `ID`: No Description +- `Status`: No Description !!! note - Using Properties Properties are what [Filters](../config-filtering.md) are written against in your configuration. You use the property diff --git a/docs/resources/aws-network-firewall-firewall.md b/docs/resources/network-firewall.md similarity index 93% rename from docs/resources/aws-network-firewall-firewall.md rename to docs/resources/network-firewall.md index 9784a352..f5ee57f9 100644 --- a/docs/resources/aws-network-firewall-firewall.md +++ b/docs/resources/network-firewall.md @@ -2,13 +2,13 @@ generated: true --- -# AWS::NetworkFirewall::Firewall +# NetworkFirewall ## Resource ```text -AWS::NetworkFirewall::Firewall +NetworkFirewall ``` ### Alternative Resource diff --git a/docs/resources/resource-explorer-2index.md b/docs/resources/resource-explorer-2index.md index fcac6e61..244bb6de 100644 --- a/docs/resources/resource-explorer-2index.md +++ b/docs/resources/resource-explorer-2index.md @@ -11,8 +11,12 @@ generated: true ResourceExplorer2Index ``` +## Properties +- `ARN`: No Description +- `Type`: No Description + !!! note - Using Properties Properties are what [Filters](../config-filtering.md) are written against in your configuration. You use the property names to write filters for what you want to **keep** and omit from the nuke process. diff --git a/docs/resources/resource-explorer-2view.md b/docs/resources/resource-explorer-2view.md index a2841730..44bc5f9a 100644 --- a/docs/resources/resource-explorer-2view.md +++ b/docs/resources/resource-explorer-2view.md @@ -11,8 +11,11 @@ generated: true ResourceExplorer2View ``` +## Properties +- `ARN`: The ARN of the Resource Explorer View + !!! note - Using Properties Properties are what [Filters](../config-filtering.md) are written against in your configuration. You use the property names to write filters for what you want to **keep** and omit from the nuke process. diff --git a/docs/resources/route-53-resource-record-set.md b/docs/resources/route-53-resource-record-set.md index 608876d1..558d85ec 100644 --- a/docs/resources/route-53-resource-record-set.md +++ b/docs/resources/route-53-resource-record-set.md @@ -11,8 +11,15 @@ generated: true Route53ResourceRecordSet ``` +## Properties +- `HostedZoneName`: The name of the zone to which the record belongs +- `Name`: The name of the record +- `Type`: The type of the record +- `tag::`: This resource has tags with property `Tags`. These are key/value pairs that are + added as their own property with the prefix of `tag:` (e.g. [tag:example: "value"]) + !!! note - Using Properties Properties are what [Filters](../config-filtering.md) are written against in your configuration. You use the property names to write filters for what you want to **keep** and omit from the nuke process. diff --git a/docs/resources/s3-access-grants-grant.md b/docs/resources/s3-access-grants-grant.md new file mode 100644 index 00000000..3976c384 --- /dev/null +++ b/docs/resources/s3-access-grants-grant.md @@ -0,0 +1,34 @@ +--- +generated: true +--- + +# S3AccessGrantsGrant + + +## Resource + +```text +S3AccessGrantsGrant +``` + +## Properties + + +- `CreatedAt`: The date and time the access grant was created. +- `GrantScope`: The scope of the access grant. +- `GranteeID`: The ARN of the grantee. +- `GranteeType`: The type of the grantee, (e.g. IAM). +- `ID`: The ID of the access grant. + +!!! note - Using Properties + Properties are what [Filters](../config-filtering.md) are written against in your configuration. You use the property + names to write filters for what you want to **keep** and omit from the nuke process. + +### String Property + +The string representation of a resource is generally the value of the Name, ID or ARN field of the resource. Not all +resources support properties. To write a filter against the string representation, simply omit the `property` field in +the filter. + +The string value is always what is used in the output of the log format when a resource is identified. + diff --git a/docs/resources/s3-access-grants-instance.md b/docs/resources/s3-access-grants-instance.md new file mode 100644 index 00000000..8d313ae5 --- /dev/null +++ b/docs/resources/s3-access-grants-instance.md @@ -0,0 +1,31 @@ +--- +generated: true +--- + +# S3AccessGrantsInstance + + +## Resource + +```text +S3AccessGrantsInstance +``` + +## Properties + + +- `CreatedAt`: The time the access grants instance was created. +- `ID`: The ID of the access grants instance. + +!!! note - Using Properties + Properties are what [Filters](../config-filtering.md) are written against in your configuration. You use the property + names to write filters for what you want to **keep** and omit from the nuke process. + +### String Property + +The string representation of a resource is generally the value of the Name, ID or ARN field of the resource. Not all +resources support properties. To write a filter against the string representation, simply omit the `property` field in +the filter. + +The string value is always what is used in the output of the log format when a resource is identified. + diff --git a/docs/resources/s3-access-grants-location.md b/docs/resources/s3-access-grants-location.md new file mode 100644 index 00000000..e5396b29 --- /dev/null +++ b/docs/resources/s3-access-grants-location.md @@ -0,0 +1,32 @@ +--- +generated: true +--- + +# S3AccessGrantsLocation + + +## Resource + +```text +S3AccessGrantsLocation +``` + +## Properties + + +- `CreatedAt`: The time the access grants location was created. +- `ID`: The ID of the access grants location. +- `LocationScope`: The scope of the access grants location. + +!!! note - Using Properties + Properties are what [Filters](../config-filtering.md) are written against in your configuration. You use the property + names to write filters for what you want to **keep** and omit from the nuke process. + +### String Property + +The string representation of a resource is generally the value of the Name, ID or ARN field of the resource. Not all +resources support properties. To write a filter against the string representation, simply omit the `property` field in +the filter. + +The string value is always what is used in the output of the log format when a resource is identified. + diff --git a/docs/resources/s3-access-point.md b/docs/resources/s3-access-point.md index e7ac6720..710ed99a 100644 --- a/docs/resources/s3-access-point.md +++ b/docs/resources/s3-access-point.md @@ -11,8 +11,15 @@ generated: true S3AccessPoint ``` +## Properties +- `ARN`: No Description +- `Alias`: No Description +- `Bucket`: No Description +- `Name`: No Description +- `NetworkOrigin`: No Description + !!! note - Using Properties Properties are what [Filters](../config-filtering.md) are written against in your configuration. You use the property names to write filters for what you want to **keep** and omit from the nuke process. diff --git a/docs/resources/s3-bucket.md b/docs/resources/s3-bucket.md index 8212bffa..71bdf295 100644 --- a/docs/resources/s3-bucket.md +++ b/docs/resources/s3-bucket.md @@ -23,7 +23,11 @@ AWS::S3::Bucket ## Properties +- `CreationDate`: No Description +- `Name`: No Description - `ObjectLock`: No Description +- `tag::`: This resource has tags with property `Tags`. These are key/value pairs that are + added as their own property with the prefix of `tag:` (e.g. [tag:example: "value"]) !!! note - Using Properties Properties are what [Filters](../config-filtering.md) are written against in your configuration. You use the property diff --git a/docs/resources/s3-multipart-upload.md b/docs/resources/s3-multipart-upload.md index bb17d334..da9ead91 100644 --- a/docs/resources/s3-multipart-upload.md +++ b/docs/resources/s3-multipart-upload.md @@ -11,8 +11,13 @@ generated: true S3MultipartUpload ``` +## Properties +- `Bucket`: No Description +- `Key`: No Description +- `UploadID`: No Description + !!! note - Using Properties Properties are what [Filters](../config-filtering.md) are written against in your configuration. You use the property names to write filters for what you want to **keep** and omit from the nuke process. diff --git a/docs/resources/s3-object.md b/docs/resources/s3-object.md index 5c70b67e..13eb4392 100644 --- a/docs/resources/s3-object.md +++ b/docs/resources/s3-object.md @@ -11,8 +11,15 @@ generated: true S3Object ``` +## Properties +- `Bucket`: No Description +- `CreationDate`: No Description +- `IsLatest`: No Description +- `Key`: No Description +- `VersionID`: No Description + !!! note - Using Properties Properties are what [Filters](../config-filtering.md) are written against in your configuration. You use the property names to write filters for what you want to **keep** and omit from the nuke process. diff --git a/docs/resources/sfn-state-machine.md b/docs/resources/sfn-state-machine.md index 0fd40a6f..e4e3de89 100644 --- a/docs/resources/sfn-state-machine.md +++ b/docs/resources/sfn-state-machine.md @@ -14,7 +14,12 @@ SFNStateMachine ## Properties -- `ARN`: No Description +- `ARN`: The Amazon Resource Name (ARN) that identifies the state machine. +- `CreationDate`: The date the state machine was created. +- `Name`: The name of the state machine. +- `Type`: The type of the state machine. +- `tag::`: This resource has tags with property `Tags`. These are key/value pairs that are + added as their own property with the prefix of `tag:` (e.g. [tag:example: "value"]) !!! note - Using Properties Properties are what [Filters](../config-filtering.md) are written against in your configuration. You use the property diff --git a/docs/resources/ssm-quick-setup-configuration-manager.md b/docs/resources/ssm-quick-setup-configuration-manager.md new file mode 100644 index 00000000..ba66cfaa --- /dev/null +++ b/docs/resources/ssm-quick-setup-configuration-manager.md @@ -0,0 +1,31 @@ +--- +generated: true +--- + +# SSMQuickSetupConfigurationManager + + +## Resource + +```text +SSMQuickSetupConfigurationManager +``` + +## Properties + + +- `ARN`: No Description +- `Name`: No Description + +!!! note - Using Properties + Properties are what [Filters](../config-filtering.md) are written against in your configuration. You use the property + names to write filters for what you want to **keep** and omit from the nuke process. + +### String Property + +The string representation of a resource is generally the value of the Name, ID or ARN field of the resource. Not all +resources support properties. To write a filter against the string representation, simply omit the `property` field in +the filter. + +The string value is always what is used in the output of the log format when a resource is identified. + diff --git a/docs/resources/transfer-web-app.md b/docs/resources/transfer-web-app.md new file mode 100644 index 00000000..38078214 --- /dev/null +++ b/docs/resources/transfer-web-app.md @@ -0,0 +1,30 @@ +--- +generated: true +--- + +# TransferWebApp + + +## Resource + +```text +TransferWebApp +``` + +## Properties + + +- `ID`: No Description + +!!! note - Using Properties + Properties are what [Filters](../config-filtering.md) are written against in your configuration. You use the property + names to write filters for what you want to **keep** and omit from the nuke process. + +### String Property + +The string representation of a resource is generally the value of the Name, ID or ARN field of the resource. Not all +resources support properties. To write a filter against the string representation, simply omit the `property` field in +the filter. + +The string value is always what is used in the output of the log format when a resource is identified. + diff --git a/go.mod b/go.mod index 415f789c..7f3e98bd 100644 --- a/go.mod +++ b/go.mod @@ -3,19 +3,30 @@ module github.com/ekristen/aws-nuke/v3 go 1.21.6 require ( - github.com/aws/aws-sdk-go v1.54.20 - github.com/aws/aws-sdk-go-v2 v1.32.6 - github.com/aws/aws-sdk-go-v2/config v1.28.6 - github.com/aws/aws-sdk-go-v2/credentials v1.17.47 - github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0 - github.com/aws/aws-sdk-go-v2/service/sts v1.33.2 + github.com/aws/aws-sdk-go v1.55.5 + github.com/aws/aws-sdk-go-v2 v1.32.7 + github.com/aws/aws-sdk-go-v2/config v1.28.7 + github.com/aws/aws-sdk-go-v2/credentials v1.17.48 + github.com/aws/aws-sdk-go-v2/service/appstream v1.42.0 + github.com/aws/aws-sdk-go-v2/service/iam v1.38.3 + github.com/aws/aws-sdk-go-v2/service/kafka v1.38.9 + github.com/aws/aws-sdk-go-v2/service/networkfirewall v1.44.7 + github.com/aws/aws-sdk-go-v2/service/s3 v1.71.1 + github.com/aws/aws-sdk-go-v2/service/s3control v1.52.1 + github.com/aws/aws-sdk-go-v2/service/sagemaker v1.171.0 + github.com/aws/aws-sdk-go-v2/service/ssmquicksetup v1.3.2 + github.com/aws/aws-sdk-go-v2/service/sts v1.33.3 + github.com/aws/aws-sdk-go-v2/service/transfer v1.55.1 + github.com/aws/aws-sdk-go-v2/service/wafregional v1.25.8 github.com/aws/smithy-go v1.22.1 - github.com/ekristen/libnuke v0.21.8 + github.com/ekristen/libnuke v0.24.0 github.com/fatih/color v1.18.0 github.com/golang/mock v1.6.0 github.com/google/uuid v1.6.0 github.com/gotidy/ptr v1.4.0 + github.com/iancoleman/strcase v0.3.0 github.com/pkg/errors v0.9.1 + github.com/rebuy-de/aws-nuke/v2 v2.25.0 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.10.0 github.com/urfave/cli/v2 v2.27.5 @@ -26,17 +37,17 @@ require ( require ( github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.21 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.25 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.26 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.6 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.6 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.24.7 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.7 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.24.8 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7 // indirect github.com/benbjohnson/clock v1.3.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -50,10 +61,7 @@ require ( github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/stevenle/topsort v0.2.0 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect - golang.org/x/mod v0.17.0 // indirect golang.org/x/sync v0.10.0 // indirect golang.org/x/sys v0.25.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index aefda508..186a866b 100644 --- a/go.sum +++ b/go.sum @@ -1,131 +1,69 @@ -github.com/aws/aws-sdk-go v1.54.20 h1:FZ2UcXya7bUkvkpf7TaPmiL7EubK0go1nlXGLRwEsoo= -github.com/aws/aws-sdk-go v1.54.20/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= -github.com/aws/aws-sdk-go-v2 v1.32.2 h1:AkNLZEyYMLnx/Q/mSKkcMqwNFXMAvFto9bNsHqcTduI= -github.com/aws/aws-sdk-go-v2 v1.32.2/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo= -github.com/aws/aws-sdk-go-v2 v1.32.3 h1:T0dRlFBKcdaUPGNtkBSwHZxrtis8CQU17UpNBZYd0wk= -github.com/aws/aws-sdk-go-v2 v1.32.3/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo= -github.com/aws/aws-sdk-go-v2 v1.32.6 h1:7BokKRgRPuGmKkFMhEg/jSul+tB9VvXhcViILtfG8b4= -github.com/aws/aws-sdk-go-v2 v1.32.6/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 h1:pT3hpW0cOHRJx8Y0DfJUEQuqPild8jRGmSFmBgvydr0= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6/go.mod h1:j/I2++U0xX+cr44QjHay4Cvxj6FUbnxrgmqN3H1jTZA= +github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= +github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go-v2 v1.32.7 h1:ky5o35oENWi0JYWUZkB7WYvVPP+bcRF5/Iq7JWSb5Rw= +github.com/aws/aws-sdk-go-v2 v1.32.7/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 h1:lL7IfaFzngfx0ZwUGOZdsFFnQ5uLvR0hWqqhyE7Q9M8= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7/go.mod h1:QraP0UcVlQJsmHfioCrveWOC1nbiWUl3ej08h4mXWoc= -github.com/aws/aws-sdk-go-v2/config v1.27.43 h1:p33fDDihFC390dhhuv8nOmX419wjOSDQRb+USt20RrU= -github.com/aws/aws-sdk-go-v2/config v1.27.43/go.mod h1:pYhbtvg1siOOg8h5an77rXle9tVG8T+BWLWAo7cOukc= -github.com/aws/aws-sdk-go-v2/config v1.28.1 h1:oxIvOUXy8x0U3fR//0eq+RdCKimWI900+SV+10xsCBw= -github.com/aws/aws-sdk-go-v2/config v1.28.1/go.mod h1:bRQcttQJiARbd5JZxw6wG0yIK3eLeSCPdg6uqmmlIiI= -github.com/aws/aws-sdk-go-v2/config v1.28.6 h1:D89IKtGrs/I3QXOLNTH93NJYtDhm8SYa9Q5CsPShmyo= -github.com/aws/aws-sdk-go-v2/config v1.28.6/go.mod h1:GDzxJ5wyyFSCoLkS+UhGB0dArhb9mI+Co4dHtoTxbko= -github.com/aws/aws-sdk-go-v2/credentials v1.17.41 h1:7gXo+Axmp+R4Z+AK8YFQO0ZV3L0gizGINCOWxSLY9W8= -github.com/aws/aws-sdk-go-v2/credentials v1.17.41/go.mod h1:u4Eb8d3394YLubphT4jLEwN1rLNq2wFOlT6OuxFwPzU= -github.com/aws/aws-sdk-go-v2/credentials v1.17.42 h1:sBP0RPjBU4neGpIYyx8mkU2QqLPl5u9cmdTWVzIpHkM= -github.com/aws/aws-sdk-go-v2/credentials v1.17.42/go.mod h1:FwZBfU530dJ26rv9saAbxa9Ej3eF/AK0OAY86k13n4M= -github.com/aws/aws-sdk-go-v2/credentials v1.17.47 h1:48bA+3/fCdi2yAwVt+3COvmatZ6jUDNkDTIsqDiMUdw= -github.com/aws/aws-sdk-go-v2/credentials v1.17.47/go.mod h1:+KdckOejLW3Ks3b0E3b5rHsr2f9yuORBum0WPnE5o5w= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17 h1:TMH3f/SCAWdNtXXVPPu5D6wrr4G5hI1rAxbcocKfC7Q= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17/go.mod h1:1ZRXLdTpzdJb9fwTMXiLipENRxkGMTn1sfKexGllQCw= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.18 h1:68jFVtt3NulEzojFesM/WVarlFpCaXLKaBxDpzkQ9OQ= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.18/go.mod h1:Fjnn5jQVIo6VyedMc0/EhPpfNlPl7dHV916O6B+49aE= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.21 h1:AmoU1pziydclFT/xRV+xXE/Vb8fttJCLRPv8oAkprc0= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.21/go.mod h1:AjUdLYe4Tgs6kpH4Bv7uMZo7pottoyHMn4eTcIcneaY= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21 h1:UAsR3xA31QGf79WzpG/ixT9FZvQlh5HY1NRqSHBNOCk= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21/go.mod h1:JNr43NFf5L9YaG3eKTm7HQzls9J+A9YYcGI5Quh1r2Y= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.22 h1:Jw50LwEkVjuVzE1NzkhNKkBf9cRN7MtE1F/b2cOKTUM= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.22/go.mod h1:Y/SmAyPcOTmpeVaWSzSKiILfXTVJwrGmYZhcRbhWuEY= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25 h1:s/fF4+yDQDoElYhfIVvSNyeCydfbuTKzhxSXDXCPasU= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25/go.mod h1:IgPfDv5jqFIzQSNbUEMoitNooSMXjRSDkhXv8jiROvU= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21 h1:6jZVETqmYCadGFvrYEQfC5fAQmlo80CeL5psbno6r0s= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21/go.mod h1:1SR0GbLlnN3QUmYaflZNiH1ql+1qrSiB2vwcJ+4UM60= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.22 h1:981MHwBaRZM7+9QSR6XamDzF/o7ouUGxFzr+nVSIhrs= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.22/go.mod h1:1RA1+aBEfn+CAB/Mh0MB6LsdCYCnjZm7tKXtnk499ZQ= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25 h1:ZntTCl5EsYnhN/IygQEUugpdwbhdkom9uHcbCftiGgA= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25/go.mod h1:DBdPrgeocww+CSl1C8cEV8PN1mHMBhuCDLpXezyvWkE= +github.com/aws/aws-sdk-go-v2/config v1.28.7 h1:GduUnoTXlhkgnxTD93g1nv4tVPILbdNQOzav+Wpg7AE= +github.com/aws/aws-sdk-go-v2/config v1.28.7/go.mod h1:vZGX6GVkIE8uECSUHB6MWAUsd4ZcG2Yq/dMa4refR3M= +github.com/aws/aws-sdk-go-v2/credentials v1.17.48 h1:IYdLD1qTJ0zanRavulofmqut4afs45mOWEI+MzZtTfQ= +github.com/aws/aws-sdk-go-v2/credentials v1.17.48/go.mod h1:tOscxHN3CGmuX9idQ3+qbkzrjVIx32lqDSU1/0d/qXs= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22 h1:kqOrpojG71DxJm/KDPO+Z/y1phm1JlC8/iT+5XRmAn8= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22/go.mod h1:NtSFajXVVL8TA2QNngagVZmUtXciyrHOt7xgz4faS/M= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 h1:I/5wmGMffY4happ8NOCuIUEWGUvvFp5NSeQcXl9RHcI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26/go.mod h1:FR8f4turZtNy6baO0KJ5FJUmXH/cSkI9fOngs0yl6mA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 h1:zXFLuEuMMUOvEARXFUVJdfqZ4bvvSgdGRq/ATcrQxzM= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26/go.mod h1:3o2Wpy0bogG1kyOPrgkXA8pgIfEEv0+m19O9D5+W8y8= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.21 h1:7edmS3VOBDhK00b/MwGtGglCm7hhwNYnjJs/PgFdMQE= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.21/go.mod h1:Q9o5h4HoIWG8XfzxqiuK/CGUbepCJ8uTlaE3bAbxytQ= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.22 h1:yV+hCAHZZYJQcwAaszoBNwLbPItHvApxT0kVIw6jRgs= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.22/go.mod h1:kbR1TL8llqB1eGnVbybcA4/wgScxdylOdyAd51yxPdw= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.25 h1:r67ps7oHCYnflpgDy2LZU0MAQtQbYIOqNNnqGO6xQkE= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.25/go.mod h1:GrGY+Q4fIokYLtjCVB/aFfCVL6hhGUFl8inD18fDalE= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 h1:TToQNkvGguu209puTojY/ozlqy2d/SFNcoLIqTFi42g= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0/go.mod h1:0jp+ltwkf+SwG2fm/PKo8t4y8pJSgOCO4D8Lz3k0aHQ= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.26 h1:GeNJsIFHB+WW5ap2Tec4K6dzcVTsRbsT1Lra46Hv9ME= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.26/go.mod h1:zfgMpwHDXX2WGoG84xG2H+ZlPTkJUU4YUvx2svLQYWo= +github.com/aws/aws-sdk-go-v2/service/appstream v1.42.0 h1:d36PMCY+OwgPqJfgPvByG2gjel2e0mcMt0JNEYMeFm4= +github.com/aws/aws-sdk-go-v2/service/appstream v1.42.0/go.mod h1:HeQk2RRQrmFPeFN1vcg9ZUx85LNH154LjGWaHW6hD3k= +github.com/aws/aws-sdk-go-v2/service/iam v1.38.3 h1:2sFIoFzU1IEL9epJWubJm9Dhrn45aTNEJuwsesaCGnk= +github.com/aws/aws-sdk-go-v2/service/iam v1.38.3/go.mod h1:KzlNINwfr/47tKkEhgk0r10/OZq3rjtyWy0txL3lM+I= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.2 h1:4FMHqLfk0efmTqhXVRL5xYRqlEBNBiRI7N6w4jsEdd4= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.2/go.mod h1:LWoqeWlK9OZeJxsROW2RqrSPvQHKTpp69r/iDjwsSaw= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.3 h1:kT6BcZsmMtNkP/iYMcRG+mIEA/IbeiUimXtGmqF39y0= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.3/go.mod h1:Z8uGua2k4PPaGOYn66pK02rhMrot3Xk3tpBuUFPomZU= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.6 h1:HCpPsWqmYQieU7SS6E9HXfdAMSud0pteVXieJmcpIRI= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.6/go.mod h1:ngUiVRCco++u+soRRVBIvBZxSMMvOVMXA4PJ36JLfSw= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2 h1:s7NA1SOw8q/5c0wr8477yOPp0z+uBaXBnLE0XYb0POA= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2/go.mod h1:fnjjWyAW/Pj5HYOxl9LJqWtEwS7W2qgcRLWP+uWbss0= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.3 h1:qcxX0JYlgWH3hpPUnd6U0ikcl6LLA9sLkXE2w1fpMvY= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.3/go.mod h1:cLSNEmI45soc+Ef8K/L+8sEA3A3pYFEYf5B5UI+6bH4= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.6 h1:50+XsN70RS7dwJ2CkVNXzj7U2L1HKP8nqTd3XWEXBN4= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.6/go.mod h1:WqgLmwY7so32kG01zD8CPTJWVWM+TzJoOVHwTg4aPug= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.2 h1:t7iUP9+4wdc5lt3E41huP+GvQZJD38WLsgVp4iOtAjg= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.2/go.mod h1:/niFCtmuQNxqx9v8WAPq5qh7EH25U4BF6tjoyq9bObM= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.3 h1:ZC7Y/XgKUxwqcdhO5LE8P6oGP1eh6xlQReWNKfhvJno= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.3/go.mod h1:WqfO7M9l9yUAw0HcHaikwRd/H6gzYdz7vjejCA5e2oY= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6 h1:BbGDtTi0T1DYlmjBiCr/le3wzhA37O8QTC5/Ab8+EXk= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6/go.mod h1:hLMJt7Q8ePgViKupeymbqI0la+t9/iYFBjxQCFwuAwI= -github.com/aws/aws-sdk-go-v2/service/s3 v1.65.2 h1:yi8m+jepdp6foK14xXLGkYBenxnlcfJ45ka4Pg7fDSQ= -github.com/aws/aws-sdk-go-v2/service/s3 v1.65.2/go.mod h1:cB6oAuus7YXRZhWCc1wIwPywwZ1XwweNp2TVAEGYeB8= -github.com/aws/aws-sdk-go-v2/service/s3 v1.66.2 h1:p9TNFL8bFUMd+38YIpTAXpoxyz0MxC7FlbFEH4P4E1U= -github.com/aws/aws-sdk-go-v2/service/s3 v1.66.2/go.mod h1:fNjyo0Coen9QTwQLWeV6WO2Nytwiu+cCcWaTdKCAqqE= -github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0 h1:nyuzXooUNJexRT0Oy0UQY6AhOzxPxhtt4DcBIHyCnmw= -github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0/go.mod h1:sT/iQz8JK3u/5gZkT+Hmr7GzVZehUMkRZpOaAwYXeGY= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.2 h1:bSYXVyUzoTHoKalBmwaZxs97HU9DWWI3ehHSAMa7xOk= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.2/go.mod h1:skMqY7JElusiOUjMJMOv1jJsP7YUg7DrhgqZZWuzu1U= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.3 h1:UTpsIf0loCIWEbrqdLb+0RxnTXfWh2vhw4nQmFi4nPc= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.3/go.mod h1:FZ9j3PFHHAR+w0BSEjK955w5YD2UwB/l/H0yAK3MJvI= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.7 h1:rLnYAfXQ3YAccocshIH5mzNNwZBkBo+bP6EhIxak6Hw= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.7/go.mod h1:ZHtuQJ6t9A/+YDuxOLnbryAmITtr8UysSny3qcyvJTc= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2 h1:AhmO1fHINP9vFYUE0LHzCWg/LfUWUF+zFPEcY9QXb7o= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2/go.mod h1:o8aQygT2+MVP0NaV6kbdE1YnnIM8RRVQzoeUH45GOdI= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.3 h1:2YCmIXv3tmiItw0LlYf6v7gEHebLY45kBEnPezbUKyU= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.3/go.mod h1:u19stRyNPxGhj6dRm+Cdgu6N75qnbW7+QN0q0dsAk58= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6 h1:JnhTZR3PiYDNKlXy50/pNeix9aGMo6lLpXwJ1mw8MD4= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6/go.mod h1:URronUEGfXZN1VpdktPSD1EkAL9mfrV+2F4sjH38qOY= -github.com/aws/aws-sdk-go-v2/service/sts v1.32.2 h1:CiS7i0+FUe+/YY1GvIBLLrR/XNGZ4CtM1Ll0XavNuVo= -github.com/aws/aws-sdk-go-v2/service/sts v1.32.2/go.mod h1:HtaiBI8CjYoNVde8arShXb94UbQQi9L4EMr6D+xGBwo= -github.com/aws/aws-sdk-go-v2/service/sts v1.32.3 h1:wVnQ6tigGsRqSWDEEyH6lSAJ9OyFUsSnbaUWChuSGzs= -github.com/aws/aws-sdk-go-v2/service/sts v1.32.3/go.mod h1:VZa9yTFyj4o10YGsmDO4gbQJUvvhY72fhumT8W4LqsE= -github.com/aws/aws-sdk-go-v2/service/sts v1.33.2 h1:s4074ZO1Hk8qv65GqNXqDjmkf4HSQqJukaLuuW0TpDA= -github.com/aws/aws-sdk-go-v2/service/sts v1.33.2/go.mod h1:mVggCnIWoM09jP71Wh+ea7+5gAp53q+49wDFs1SW5z8= -github.com/aws/smithy-go v1.22.0 h1:uunKnWlcoL3zO7q+gG2Pk53joueEOsnNB28QdMsmiMM= -github.com/aws/smithy-go v1.22.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.7 h1:tB4tNw83KcajNAzaIMhkhVI2Nt8fAZd5A5ro113FEMY= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.7/go.mod h1:lvpyBGkZ3tZ9iSsUIcC2EWp+0ywa7aK3BLT+FwZi+mQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7 h1:8eUsivBQzZHqe/3FE+cqwfH+0p5Jo8PFM/QYQSmeZ+M= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7/go.mod h1:kLPQvGUmxn/fqiCrDeohwG33bq2pQpGeY62yRO6Nrh0= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.7 h1:Hi0KGbrnr57bEHWM0bJ1QcBzxLrL/k2DHvGYhb8+W1w= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.7/go.mod h1:wKNgWgExdjjrm4qvfbTorkvocEstaoDl4WCvGfeCy9c= +github.com/aws/aws-sdk-go-v2/service/kafka v1.38.9 h1:2y8YEVPTiGBPPoHBLWU+hEnbA/rLVCEWkeF9nD0ox38= +github.com/aws/aws-sdk-go-v2/service/kafka v1.38.9/go.mod h1:8BomElFc/tYQysVPwmZXFytmn2CwnxKW51HqYD2B29Q= +github.com/aws/aws-sdk-go-v2/service/networkfirewall v1.44.7 h1:QylOhJ/Cq8rXpdGY0TVDozlqlrioqhBGL8lr0humk/Y= +github.com/aws/aws-sdk-go-v2/service/networkfirewall v1.44.7/go.mod h1:JRNqD9oCs0lfhwggQ9try04MIaPU8lJ5XIufAUv49Ho= +github.com/aws/aws-sdk-go-v2/service/s3 v1.71.1 h1:aOVVZJgWbaH+EJYPvEgkNhCEbXXvH7+oML36oaPK3zE= +github.com/aws/aws-sdk-go-v2/service/s3 v1.71.1/go.mod h1:r+xl5yzMk9083rMR+sJ5TYj9Tihvf/l1oxzZXDgGj2Q= +github.com/aws/aws-sdk-go-v2/service/s3control v1.52.1 h1:xxGbXbGtO/VMz2JqB1UwEDlSchryUss0KmQJSZ0oTUE= +github.com/aws/aws-sdk-go-v2/service/s3control v1.52.1/go.mod h1:6BuUa52of67a+ri/poTH82XiL+rTGQWUPZCmf2cfVHI= +github.com/aws/aws-sdk-go-v2/service/sagemaker v1.171.0 h1:LDOuosm4ZFlLMglxAqO94T5SZ6Jaawa1e2hiUAqWAts= +github.com/aws/aws-sdk-go-v2/service/sagemaker v1.171.0/go.mod h1:hzu5ncs1K7l08GCup8WRVxw/uqgw1BQLDyfVomRM3sY= +github.com/aws/aws-sdk-go-v2/service/ssmquicksetup v1.3.2 h1:4siT1z3nEVxJq1jZYu1SRoct5xgbKen+ammCuZBZ2zI= +github.com/aws/aws-sdk-go-v2/service/ssmquicksetup v1.3.2/go.mod h1:KSO1+erW2SUB6Mw/Qamu1fOT5fn/mzd9G79ENbYqyRQ= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.8 h1:CvuUmnXI7ebaUAhbJcDy9YQx8wHR69eZ9I7q5hszt/g= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.8/go.mod h1:XDeGv1opzwm8ubxddF0cgqkZWsyOtw4lr6dxwmb6YQg= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7 h1:F2rBfNAL5UyswqoeWv9zs74N/NanhK16ydHW1pahX6E= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7/go.mod h1:JfyQ0g2JG8+Krq0EuZNnRwX0mU0HrwY/tG6JNfcqh4k= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.3 h1:Xgv/hyNgvLda/M9l9qxXc4UFSgppnRczLxlMs5Ae/QY= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.3/go.mod h1:5Gn+d+VaaRgsjewpMvGazt0WfcFO+Md4wLOuBfGR9Bc= +github.com/aws/aws-sdk-go-v2/service/transfer v1.55.1 h1:bENkaFtA6rxHAwNPjYbgwYxUHGJbL7QocCt8nKZ7d10= +github.com/aws/aws-sdk-go-v2/service/transfer v1.55.1/go.mod h1:C7x9hpm90ZocJ9GbauHMkVMU0m7knEiKhOaa4um9tBU= +github.com/aws/aws-sdk-go-v2/service/wafregional v1.25.8 h1:LG+3JxLZqa1wGrp9bCA0mP+jeqGokKEleLmk9cgnvUw= +github.com/aws/aws-sdk-go-v2/service/wafregional v1.25.8/go.mod h1:k5QM358GxnQJ19Y/XGCnY8a6pLrb2CfvC2S59UKq0ok= github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro= github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/ekristen/libnuke v0.19.1 h1:n52PMccQjs4MsaYPtulavxmKyHFq4xz3KCy6mpjoX/I= -github.com/ekristen/libnuke v0.19.1/go.mod h1:riI1tjCf6r+et/9oUBd1vQeFmn2Sn6UeFUR0nWkMeYw= -github.com/ekristen/libnuke v0.19.2 h1:dlmqeHBHaQN+gv6Cg7+DwehpayocAABTYzSaTmaP6Pk= -github.com/ekristen/libnuke v0.19.2/go.mod h1:DIN5VmrH6AUwaXc25RHcH/V+JKALdl16CN9iJvFtbK4= -github.com/ekristen/libnuke v0.20.0 h1:GV6ebfPt3ac+5ygto3hdIH5PN9ppXPAAJo7C00ngOCI= -github.com/ekristen/libnuke v0.20.0/go.mod h1:DIN5VmrH6AUwaXc25RHcH/V+JKALdl16CN9iJvFtbK4= -github.com/ekristen/libnuke v0.21.0 h1:8bBlx4Bj9WP1inxz6+iGxXW6V2iDDJidbT+0xsQDlLE= -github.com/ekristen/libnuke v0.21.0/go.mod h1:DIN5VmrH6AUwaXc25RHcH/V+JKALdl16CN9iJvFtbK4= -github.com/ekristen/libnuke v0.21.1 h1:fngmzmV2JyjDagxh8SzPDcJ5dvmhDjvbU+XhA1vsHPs= -github.com/ekristen/libnuke v0.21.1/go.mod h1:DIN5VmrH6AUwaXc25RHcH/V+JKALdl16CN9iJvFtbK4= -github.com/ekristen/libnuke v0.21.3 h1:AF5oY1jcgbxycDqYOgdYTtVm9jl6Uyj4IRea4mIfCSU= -github.com/ekristen/libnuke v0.21.3/go.mod h1:FgzH3PKJqk/31Lty+B3MFwjWc7fz+NpGylVtgzIDFII= -github.com/ekristen/libnuke v0.21.4 h1:krEZZIAITp/begn/ltNfp9xJ7WznUI/tnEPCIFmHVas= -github.com/ekristen/libnuke v0.21.4/go.mod h1:FgzH3PKJqk/31Lty+B3MFwjWc7fz+NpGylVtgzIDFII= -github.com/ekristen/libnuke v0.21.8 h1:JyMyMUbh/ti8r1wjXe5B8h8xhPibFN2tu9zVlkn1vyw= -github.com/ekristen/libnuke v0.21.8/go.mod h1:+hh3UCSxmkfBweQJv9pa5twY82n7MhO4DK+AA+oUoTM= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/ekristen/libnuke v0.24.0 h1:DCzkOwT+n/2G37KoNSlTOU1DoAVcBgtdaJSNpQl6NJw= +github.com/ekristen/libnuke v0.24.0/go.mod h1:+hh3UCSxmkfBweQJv9pa5twY82n7MhO4DK+AA+oUoTM= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= @@ -134,15 +72,14 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gotidy/ptr v1.4.0 h1:7++suUs+HNHMnyz6/AW3SE+4EnBhupPSQTSI7QNijVc= github.com/gotidy/ptr v1.4.0/go.mod h1:MjRBG6/IETiiZGWI8LrRtISXEji+8b/jigmj2q0mEyM= +github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -157,6 +94,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rebuy-de/aws-nuke/v2 v2.25.0 h1:uM/KoDOOIau1Gcx++D3oDFL1vlLPj9uuzQKtNVZxrHs= +github.com/rebuy-de/aws-nuke/v2 v2.25.0/go.mod h1:2TTX8eMpEsFZPYCK1QaAb9uPYtdO9MeLQPKM9GQfY/w= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= @@ -168,12 +107,8 @@ github.com/stevenle/topsort v0.2.0 h1:LLWgtp34HPX6/RBDRS0kElVxGOTzGBLI1lSAa5Lb46 github.com/stevenle/topsort v0.2.0/go.mod h1:ck2WG2/ZrOr6dLApQ/5Xrqy5wv3T0qhKYWE7r9tkibc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/urfave/cli/v2 v2.27.4 h1:o1owoI+02Eb+K107p27wEX9Bb8eqIoZCfLXloLUSWJ8= -github.com/urfave/cli/v2 v2.27.4/go.mod h1:m4QzxcD2qpra4z7WhzEGn74WZLViBnMpb1ToCAKdGRQ= github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= @@ -186,16 +121,11 @@ go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJh golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= -golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -206,25 +136,16 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/mkdocs.yml b/mkdocs.yml index af4bdb8a..90dccc9b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -79,6 +79,7 @@ nav: - Global Filters: features/global-filters.md - Filter Groups: features/filter-groups.md - Enabled Regions: features/enabled-regions.md + - Name Expansion: features/name-expansion.md - Signed Binaries: features/signed-binaries.md - CLI: - Usage: cli-usage.md @@ -120,7 +121,6 @@ nav: - AWS Ecr Replication Configuration: resources/aws-ecr-replication-configuration.md - AWS Mwaa Environment: resources/aws-mwaa-environment.md - AWS Network Firewall Firewall Policy: resources/aws-network-firewall-firewall-policy.md - - AWS Network Firewall Firewall: resources/aws-network-firewall-firewall.md - AWS Network Firewall Rule Group: resources/aws-network-firewall-rule-group.md - AWS Synthetics Canary: resources/aws-synthetics-canary.md - AWS Timestream Database: resources/aws-timestream-database.md @@ -486,6 +486,7 @@ nav: - Neptune Cluster: resources/neptune-cluster.md - Neptune Instance: resources/neptune-instance.md - Neptune Snapshot: resources/neptune-snapshot.md + - Network Firewall: resources/network-firewall.md - Network Manager Connect Peer: resources/network-manager-connect-peer.md - Network Manager Core Network: resources/network-manager-core-network.md - Network Manager Global Network: resources/network-manager-global-network.md @@ -542,6 +543,9 @@ nav: - Route 53 Resolver Rule: resources/route-53-resolver-rule.md - Route 53 Resource Record Set: resources/route-53-resource-record-set.md - Route 53 Traffic Policy: resources/route-53-traffic-policy.md + - S3 Access Grants Grant: resources/s3-access-grants-grant.md + - S3 Access Grants Instance: resources/s3-access-grants-instance.md + - S3 Access Grants Location: resources/s3-access-grants-location.md - S3 Access Point: resources/s3-access-point.md - S3 Bucket: resources/s3-bucket.md - S3 Multipart Upload: resources/s3-multipart-upload.md @@ -557,6 +561,7 @@ nav: - SSM Maintenance Window: resources/ssm-maintenance-window.md - SSM Parameter: resources/ssm-parameter.md - SSM Patch Baseline: resources/ssm-patch-baseline.md + - SSM Quick Setup Configuration Manager: resources/ssm-quick-setup-configuration-manager.md - SSM Resource Data Sync: resources/ssm-resource-data-sync.md - Sage Maker App: resources/sage-maker-app.md - Sage Maker Domain: resources/sage-maker-domain.md @@ -605,6 +610,7 @@ nav: - Transcribe Vocabulary: resources/transcribe-vocabulary.md - Transfer Server User: resources/transfer-server-user.md - Transfer Server: resources/transfer-server.md + - Transfer Web App: resources/transfer-web-app.md - WAF Regional Byte Match Set Ip: resources/waf-regional-byte-match-set-ip.md - WAF Regional Byte Match Set: resources/waf-regional-byte-match-set.md - WAF Regional Ip Set Ip: resources/waf-regional-ip-set-ip.md diff --git a/mocks/mock_autoscalingiface/mock.go b/mocks/mock_autoscalingiface/mock.go index ff258ab8..5e99d36c 100644 --- a/mocks/mock_autoscalingiface/mock.go +++ b/mocks/mock_autoscalingiface/mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: /home/runner/go/pkg/mod/github.com/aws/aws-sdk-go@v1.54.19/service/autoscaling/autoscalingiface/interface.go +// Source: /home/runner/go/pkg/mod/github.com/aws/aws-sdk-go@v1.55.5/service/autoscaling/autoscalingiface/interface.go // Package mock_autoscalingiface is a generated GoMock package. package mock_autoscalingiface diff --git a/mocks/mock_budgetsiface/mock.go b/mocks/mock_budgetsiface/mock.go index 6fe7c07a..3cfbcbcd 100644 --- a/mocks/mock_budgetsiface/mock.go +++ b/mocks/mock_budgetsiface/mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: /Users/ekristen/go/pkg/mod/github.com/aws/aws-sdk-go@v1.54.20/service/budgets/budgetsiface/interface.go +// Source: /home/runner/go/pkg/mod/github.com/aws/aws-sdk-go@v1.55.5/service/budgets/budgetsiface/interface.go // Package mock_budgetsiface is a generated GoMock package. package mock_budgetsiface diff --git a/mocks/mock_cloudformationiface/mock.go b/mocks/mock_cloudformationiface/mock.go index e3c8cb00..25d74838 100644 --- a/mocks/mock_cloudformationiface/mock.go +++ b/mocks/mock_cloudformationiface/mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: /home/runner/go/pkg/mod/github.com/aws/aws-sdk-go@v1.54.19/service/cloudformation/cloudformationiface/interface.go +// Source: /home/runner/go/pkg/mod/github.com/aws/aws-sdk-go@v1.55.5/service/cloudformation/cloudformationiface/interface.go // Package mock_cloudformationiface is a generated GoMock package. package mock_cloudformationiface diff --git a/mocks/mock_cognitoidentityprovideriface/mock.go b/mocks/mock_cognitoidentityprovideriface/mock.go index dfa97a78..a99d8f46 100644 --- a/mocks/mock_cognitoidentityprovideriface/mock.go +++ b/mocks/mock_cognitoidentityprovideriface/mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: /Users/ekristen/go/pkg/mod/github.com/aws/aws-sdk-go@v1.54.20/service/cognitoidentityprovider/cognitoidentityprovideriface/interface.go +// Source: /home/runner/go/pkg/mod/github.com/aws/aws-sdk-go@v1.55.5/service/cognitoidentityprovider/cognitoidentityprovideriface/interface.go // Package mock_cognitoidentityprovideriface is a generated GoMock package. package mock_cognitoidentityprovideriface diff --git a/mocks/mock_dynamodbiface/mock.go b/mocks/mock_dynamodbiface/mock.go index 8bfe43cf..0f81d40d 100644 --- a/mocks/mock_dynamodbiface/mock.go +++ b/mocks/mock_dynamodbiface/mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: /Users/ekristen/go/pkg/mod/github.com/aws/aws-sdk-go@v1.54.19/service/dynamodb/dynamodbiface/interface.go +// Source: /home/runner/go/pkg/mod/github.com/aws/aws-sdk-go@v1.55.5/service/dynamodb/dynamodbiface/interface.go // Package mock_dynamodbiface is a generated GoMock package. package mock_dynamodbiface diff --git a/mocks/mock_ecsiface/mock.go b/mocks/mock_ecsiface/mock.go index 59ee7ad3..8bbeca19 100644 --- a/mocks/mock_ecsiface/mock.go +++ b/mocks/mock_ecsiface/mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: /home/runner/go/pkg/mod/github.com/aws/aws-sdk-go@v1.54.19/service/ecs/ecsiface/interface.go +// Source: /home/runner/go/pkg/mod/github.com/aws/aws-sdk-go@v1.55.5/service/ecs/ecsiface/interface.go // Package mock_ecsiface is a generated GoMock package. package mock_ecsiface diff --git a/mocks/mock_elasticacheiface/mock.go b/mocks/mock_elasticacheiface/mock.go index b94990b7..f7b82d2f 100644 --- a/mocks/mock_elasticacheiface/mock.go +++ b/mocks/mock_elasticacheiface/mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: /home/runner/go/pkg/mod/github.com/aws/aws-sdk-go@v1.54.19/service/elasticache/elasticacheiface/interface.go +// Source: /home/runner/go/pkg/mod/github.com/aws/aws-sdk-go@v1.55.5/service/elasticache/elasticacheiface/interface.go // Package mock_elasticacheiface is a generated GoMock package. package mock_elasticacheiface diff --git a/mocks/mock_gameliftiface/mock.go b/mocks/mock_gameliftiface/mock.go index 5c4f5a0b..945a21c7 100644 --- a/mocks/mock_gameliftiface/mock.go +++ b/mocks/mock_gameliftiface/mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: /Users/ekristen/go/pkg/mod/github.com/aws/aws-sdk-go@v1.54.20/service/gamelift/gameliftiface/interface.go +// Source: /home/runner/go/pkg/mod/github.com/aws/aws-sdk-go@v1.55.5/service/gamelift/gameliftiface/interface.go // Package mock_gameliftiface is a generated GoMock package. package mock_gameliftiface diff --git a/mocks/mock_glueiface/mock.go b/mocks/mock_glueiface/mock.go index 72bd32ec..17d2290d 100644 --- a/mocks/mock_glueiface/mock.go +++ b/mocks/mock_glueiface/mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: /home/runner/go/pkg/mod/github.com/aws/aws-sdk-go@v1.54.19/service/glue/glueiface/interface.go +// Source: /home/runner/go/pkg/mod/github.com/aws/aws-sdk-go@v1.55.5/service/glue/glueiface/interface.go // Package mock_glueiface is a generated GoMock package. package mock_glueiface diff --git a/mocks/mock_iamiface/mock.go b/mocks/mock_iamiface/mock.go index 4d2c24bf..6ebcb6ab 100644 --- a/mocks/mock_iamiface/mock.go +++ b/mocks/mock_iamiface/mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: /home/runner/go/pkg/mod/github.com/aws/aws-sdk-go@v1.54.19/service/iam/iamiface/interface.go +// Source: /home/runner/go/pkg/mod/github.com/aws/aws-sdk-go@v1.55.5/service/iam/iamiface/interface.go // Package mock_iamiface is a generated GoMock package. package mock_iamiface diff --git a/mocks/mock_kmsiface/mock.go b/mocks/mock_kmsiface/mock.go index 7cb010c1..48034af5 100644 --- a/mocks/mock_kmsiface/mock.go +++ b/mocks/mock_kmsiface/mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: /Users/ekristen/go/pkg/mod/github.com/aws/aws-sdk-go@v1.54.20/service/kms/kmsiface/interface.go +// Source: /home/runner/go/pkg/mod/github.com/aws/aws-sdk-go@v1.55.5/service/kms/kmsiface/interface.go // Package mock_kmsiface is a generated GoMock package. package mock_kmsiface diff --git a/mocks/mock_pinpointsmsvoicev2iface/mock.go b/mocks/mock_pinpointsmsvoicev2iface/mock.go index 7eb1916d..87911fbc 100644 --- a/mocks/mock_pinpointsmsvoicev2iface/mock.go +++ b/mocks/mock_pinpointsmsvoicev2iface/mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: /Users/ekristen/go/pkg/mod/github.com/aws/aws-sdk-go@v1.54.20/service/pinpointsmsvoicev2/pinpointsmsvoicev2iface/interface.go +// Source: /home/runner/go/pkg/mod/github.com/aws/aws-sdk-go@v1.55.5/service/pinpointsmsvoicev2/pinpointsmsvoicev2iface/interface.go // Package mock_pinpointsmsvoicev2iface is a generated GoMock package. package mock_pinpointsmsvoicev2iface diff --git a/mocks/mock_quicksightiface/mock.go b/mocks/mock_quicksightiface/mock.go index d8b805d8..7a0b639b 100644 --- a/mocks/mock_quicksightiface/mock.go +++ b/mocks/mock_quicksightiface/mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: /home/runner/go/pkg/mod/github.com/aws/aws-sdk-go@v1.54.19/service/quicksight/quicksightiface/interface.go +// Source: /home/runner/go/pkg/mod/github.com/aws/aws-sdk-go@v1.55.5/service/quicksight/quicksightiface/interface.go // Package mock_quicksightiface is a generated GoMock package. package mock_quicksightiface diff --git a/mocks/mock_route53iface/mock.go b/mocks/mock_route53iface/mock.go index c7d73ef0..bc853083 100644 --- a/mocks/mock_route53iface/mock.go +++ b/mocks/mock_route53iface/mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: /Users/ekristen/go/pkg/mod/github.com/aws/aws-sdk-go@v1.54.20/service/route53/route53iface/interface.go +// Source: /home/runner/go/pkg/mod/github.com/aws/aws-sdk-go@v1.55.5/service/route53/route53iface/interface.go // Package mock_route53iface is a generated GoMock package. package mock_route53iface diff --git a/mocks/mock_route53resolveriface/mock.go b/mocks/mock_route53resolveriface/mock.go index 7eeabf8f..01c8dc84 100644 --- a/mocks/mock_route53resolveriface/mock.go +++ b/mocks/mock_route53resolveriface/mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: /Users/ekristen/go/pkg/mod/github.com/aws/aws-sdk-go@v1.54.20/service/route53resolver/route53resolveriface/interface.go +// Source: /home/runner/go/pkg/mod/github.com/aws/aws-sdk-go@v1.55.5/service/route53resolver/route53resolveriface/interface.go // Package mock_route53resolveriface is a generated GoMock package. package mock_route53resolveriface diff --git a/mocks/mock_sagemakeriface/mock.go b/mocks/mock_sagemakeriface/mock.go index b0e6dee3..5b7636ad 100644 --- a/mocks/mock_sagemakeriface/mock.go +++ b/mocks/mock_sagemakeriface/mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: /home/runner/go/pkg/mod/github.com/aws/aws-sdk-go@v1.54.19/service/sagemaker/sagemakeriface/interface.go +// Source: /home/runner/go/pkg/mod/github.com/aws/aws-sdk-go@v1.55.5/service/sagemaker/sagemakeriface/interface.go // Package mock_sagemakeriface is a generated GoMock package. package mock_sagemakeriface diff --git a/mocks/mock_secretsmanageriface/mock.go b/mocks/mock_secretsmanageriface/mock.go index 52fded1f..0705229b 100644 --- a/mocks/mock_secretsmanageriface/mock.go +++ b/mocks/mock_secretsmanageriface/mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: /home/runner/go/pkg/mod/github.com/aws/aws-sdk-go@v1.54.19/service/secretsmanager/secretsmanageriface/interface.go +// Source: /home/runner/go/pkg/mod/github.com/aws/aws-sdk-go@v1.55.5/service/secretsmanager/secretsmanageriface/interface.go // Package mock_secretsmanageriface is a generated GoMock package. package mock_secretsmanageriface diff --git a/mocks/mock_servicediscoveryiface/mock.go b/mocks/mock_servicediscoveryiface/mock.go index 30c6b6fc..dbd4e756 100644 --- a/mocks/mock_servicediscoveryiface/mock.go +++ b/mocks/mock_servicediscoveryiface/mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: /home/runner/go/pkg/mod/github.com/aws/aws-sdk-go@v1.54.19/service/servicediscovery/servicediscoveryiface/interface.go +// Source: /home/runner/go/pkg/mod/github.com/aws/aws-sdk-go@v1.55.5/service/servicediscovery/servicediscoveryiface/interface.go // Package mock_servicediscoveryiface is a generated GoMock package. package mock_servicediscoveryiface diff --git a/mocks/mock_sqsiface/mock.go b/mocks/mock_sqsiface/mock.go index cb78567c..2dab2425 100644 --- a/mocks/mock_sqsiface/mock.go +++ b/mocks/mock_sqsiface/mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: /home/runner/go/pkg/mod/github.com/aws/aws-sdk-go@v1.54.19/service/sqs/sqsiface/interface.go +// Source: /home/runner/go/pkg/mod/github.com/aws/aws-sdk-go@v1.55.5/service/sqs/sqsiface/interface.go // Package mock_sqsiface is a generated GoMock package. package mock_sqsiface diff --git a/pkg/commands/global/global.go b/pkg/commands/global/global.go index d104fe83..652bf097 100644 --- a/pkg/commands/global/global.go +++ b/pkg/commands/global/global.go @@ -7,6 +7,8 @@ import ( "github.com/sirupsen/logrus" "github.com/urfave/cli/v2" + + "github.com/ekristen/libnuke/pkg/log" ) func Flags() []cli.Flag { @@ -15,12 +17,13 @@ func Flags() []cli.Flag { Name: "log-level", Usage: "Log Level", Aliases: []string{"l"}, - EnvVars: []string{"LOGLEVEL"}, + EnvVars: []string{"LOGLEVEL", "AWS_NUKE_LOG_LEVEL"}, Value: "info", }, &cli.BoolFlag{ - Name: "log-caller", - Usage: "log the caller (aka line number and file)", + Name: "log-caller", + Usage: "log the caller (aka line number and file)", + EnvVars: []string{"AWS_NUKE_LOG_CALLER"}, }, &cli.BoolFlag{ Name: "log-disable-color", @@ -30,6 +33,17 @@ func Flags() []cli.Flag { Name: "log-full-timestamp", Usage: "force log output to always show full timestamp", }, + &cli.StringFlag{ + Name: "log-format", + Usage: "log format", + Value: "standard", + EnvVars: []string{"AWS_NUKE_LOG_FORMAT"}, + }, + &cli.BoolFlag{ + Name: "json", + Usage: "output as json, shorthand for --log-format=json", + EnvVars: []string{"AWS_NUKE_LOG_FORMAT_JSON"}, + }, } return globalFlags @@ -48,7 +62,29 @@ func Before(c *cli.Context) error { } } - logrus.SetFormatter(formatter) + logFormatter := &log.CustomFormatter{ + FallbackFormatter: formatter, + } + + if c.Bool("json") { + _ = c.Set("log-format", "json") + } + + switch c.String("log-format") { + case "json": + logrus.SetFormatter(&logrus.JSONFormatter{ + DisableHTMLEscape: true, + }) + // note: this is a hack to remove the _handler key from the log output + logrus.AddHook(&StructuredHook{}) + case "kv": + logrus.SetFormatter(&logrus.TextFormatter{ + DisableColors: true, + FullTimestamp: true, + }) + default: + logrus.SetFormatter(logFormatter) + } switch c.String("log-level") { case "trace": @@ -65,3 +101,20 @@ func Before(c *cli.Context) error { return nil } + +type StructuredHook struct { +} + +func (h *StructuredHook) Levels() []logrus.Level { + return logrus.AllLevels +} + +func (h *StructuredHook) Fire(e *logrus.Entry) error { + if e.Data == nil { + return nil + } + + delete(e.Data, "_handler") + + return nil +} diff --git a/pkg/commands/list/list.go b/pkg/commands/list/list.go index 7f4a580b..7bfa0633 100644 --- a/pkg/commands/list/list.go +++ b/pkg/commands/list/list.go @@ -1,7 +1,6 @@ package list import ( - "sort" "strings" "github.com/fatih/color" @@ -15,9 +14,12 @@ import ( ) func execute(c *cli.Context) error { - ls := registry.GetNames() - - sort.Strings(ls) + var ls []string + if c.Args().Len() > 0 { + ls = registry.ExpandNames(c.Args().Slice()) + } else { + ls = registry.GetNames() + } for _, name := range ls { if strings.HasPrefix(name, "AWS::") { diff --git a/pkg/commands/nuke/nuke.go b/pkg/commands/nuke/nuke.go index 67f8e0c4..8b6719e5 100644 --- a/pkg/commands/nuke/nuke.go +++ b/pkg/commands/nuke/nuke.go @@ -3,6 +3,7 @@ package nuke import ( "context" "fmt" + "os" "slices" "strings" "time" @@ -74,14 +75,17 @@ func execute(c *cli.Context) error { //nolint:funlen,gocyclo } } + logger := logrus.StandardLogger() + logger.SetOutput(os.Stdout) + // Parse the user supplied configuration file to pass in part to configure the nuke process. parsedConfig, err := config.New(libconfig.Options{ Path: c.Path("config"), Deprecations: registry.GetDeprecatedResourceTypeMapping(), - Log: logrus.WithField("component", "config"), + Log: logger.WithField("component", "config"), }) if err != nil { - logrus.Errorf("Failed to parse config file %s", c.Path("config")) + logger.Errorf("Failed to parse config file %s", c.Path("config")) return err } @@ -95,7 +99,7 @@ func execute(c *cli.Context) error { //nolint:funlen,gocyclo err = fmt.Errorf( "the custom region '%s' must be specified in the configuration 'endpoints'"+ " to determine its partition", defaultRegion) - logrus.WithError(err).Errorf("unable to resolve partition for region: %s", defaultRegion) + logger.WithError(err).Errorf("unable to resolve partition for region: %s", defaultRegion) return err } } @@ -119,8 +123,8 @@ func execute(c *cli.Context) error { //nolint:funlen,gocyclo n := libnuke.New(params, filters, parsedConfig.Settings) n.SetRunSleep(c.Duration("run-sleep-delay")) - n.SetLogger(logrus.WithField("component", "libnuke")) - n.RegisterVersion(fmt.Sprintf("> %s", common.AppVersion.String())) + n.SetLogger(logger.WithField("component", "libnuke")) + n.RegisterVersion(common.AppVersion.String()) // Register our custom validate handler that validates the account and AWS nuke unique alias checks n.RegisterValidateHandler(func() error { @@ -128,7 +132,7 @@ func execute(c *cli.Context) error { //nolint:funlen,gocyclo }) // Register our custom prompt handler that shows the account information - p := &nuke.Prompt{Parameters: params, Account: account} + p := &nuke.Prompt{Parameters: params, Account: account, Logger: logger} n.RegisterPrompt(p.Prompt) // Get any specific account level configuration @@ -139,17 +143,17 @@ func execute(c *cli.Context) error { //nolint:funlen,gocyclo resourceTypes := types.ResolveResourceTypes( registry.GetNames(), []types.Collection{ - n.Parameters.Includes, + registry.ExpandNames(n.Parameters.Includes), parsedConfig.ResourceTypes.GetIncludes(), accountConfig.ResourceTypes.GetIncludes(), }, []types.Collection{ - n.Parameters.Excludes, + registry.ExpandNames(n.Parameters.Excludes), parsedConfig.ResourceTypes.Excludes, accountConfig.ResourceTypes.Excludes, }, []types.Collection{ - n.Parameters.Alternatives, + registry.ExpandNames(n.Parameters.Alternatives), parsedConfig.ResourceTypes.GetAlternatives(), accountConfig.ResourceTypes.GetAlternatives(), }, @@ -161,23 +165,23 @@ func execute(c *cli.Context) error { //nolint:funlen,gocyclo if slices.Contains(parsedConfig.Regions, "all") { parsedConfig.Regions = account.Regions() - logrus.Info( + logger.Info( `"all" detected in region list, only enabled regions and "global" will be used, all others ignored`) if len(parsedConfig.Regions) > 1 { - logrus.Warnf(`additional regions defined along with "all", these will be ignored!`) + logger.Warnf(`additional regions defined along with "all", these will be ignored!`) } - logrus.Infof("The following regions are enabled for the account (%d total):", len(parsedConfig.Regions)) + logger.Infof("The following regions are enabled for the account (%d total):", len(parsedConfig.Regions)) printableRegions := make([]string, 0) for i, region := range parsedConfig.Regions { printableRegions = append(printableRegions, region) if i%6 == 0 { // print 5 regions per line - logrus.Infof("> %s", strings.Join(printableRegions, ", ")) + logger.Infof("> %s", strings.Join(printableRegions, ", ")) printableRegions = make([]string, 0) } else if i == len(parsedConfig.Regions)-1 { - logrus.Infof("> %s", strings.Join(printableRegions, ", ")) + logger.Infof("> %s", strings.Join(printableRegions, ", ")) } } } @@ -191,11 +195,12 @@ func execute(c *cli.Context) error { //nolint:funlen,gocyclo scannerActual := scanner.New(regionName, resourceTypes, &nuke.ListerOpts{ Region: region, AccountID: ptr.String(account.ID()), - Logger: logrus.WithFields(logrus.Fields{ + Logger: logger.WithFields(logrus.Fields{ "component": "scanner", "region": regionName, }), }) + scannerActual.SetLogger(logger) // Step 3 - Register a mutate function that will be called to modify the lister options for each resource type // see pkg/nuke/resource.go for the MutateOpts function. Its purpose is to create the proper session for the diff --git a/pkg/nuke/prompt.go b/pkg/nuke/prompt.go index d4dc1265..fb494346 100644 --- a/pkg/nuke/prompt.go +++ b/pkg/nuke/prompt.go @@ -4,6 +4,8 @@ import ( "fmt" "time" + "github.com/sirupsen/logrus" + libnuke "github.com/ekristen/libnuke/pkg/nuke" "github.com/ekristen/libnuke/pkg/utils" @@ -15,18 +17,21 @@ import ( type Prompt struct { Parameters *libnuke.Parameters Account *awsutil.Account + Logger *logrus.Logger } // Prompt is the actual function called by the libnuke process during it's run func (p *Prompt) Prompt() error { forceSleep := time.Duration(p.Parameters.ForceSleep) * time.Second - fmt.Printf("Do you really want to nuke the account with "+ - "the ID %s and the alias '%s'?\n", p.Account.ID(), p.Account.Alias()) if p.Parameters.Force { - fmt.Printf("Waiting %v before continuing.\n", forceSleep) + p.Logger.WithField("_handler", "println").Info("no-prompt flag set, continuing without prompting user") + p.Logger.WithField("_handler", "println").Infof("waiting %v before continuing", forceSleep) time.Sleep(forceSleep) } else { + fmt.Printf("Do you really want to nuke the account with "+ + "the ID %s and the alias '%s'?\n", p.Account.ID(), p.Account.Alias()) + fmt.Printf("Do you want to continue? Enter account alias to continue.\n") if err := utils.Prompt(p.Account.Alias()); err != nil { return err diff --git a/resources/appstream-images.go b/resources/appstream-images.go index 2682f807..017b0108 100644 --- a/resources/appstream-images.go +++ b/resources/appstream-images.go @@ -7,10 +7,11 @@ import ( "github.com/gotidy/ptr" - "github.com/aws/aws-sdk-go/service/appstream" + "github.com/aws/aws-sdk-go-v2/service/appstream" "github.com/ekristen/libnuke/pkg/registry" "github.com/ekristen/libnuke/pkg/resource" + "github.com/ekristen/libnuke/pkg/types" "github.com/ekristen/aws-nuke/v3/pkg/nuke" ) @@ -28,38 +29,75 @@ func init() { type AppStreamImageLister struct{} -func (l *AppStreamImageLister) List(_ context.Context, o interface{}) ([]resource.Resource, error) { +func (l *AppStreamImageLister) List(ctx context.Context, o interface{}) ([]resource.Resource, error) { opts := o.(*nuke.ListerOpts) + svc := appstream.NewFromConfig(*opts.Config) - svc := appstream.New(opts.Session) resources := make([]resource.Resource, 0) - - params := &appstream.DescribeImagesInput{} - - output, err := svc.DescribeImages(params) - if err != nil { - return nil, err - } - - for _, image := range output.Images { - resources = append(resources, &AppStreamImage{ - svc: svc, - name: image.Name, - visibility: image.Visibility, - }) + var nextToken *string + + for ok := true; ok; ok = (nextToken != nil) { + params := &appstream.DescribeImagesInput{ + NextToken: nextToken, + } + + output, err := svc.DescribeImages(ctx, params) + if err != nil { + return nil, err + } + nextToken = output.NextToken + + for _, image := range output.Images { + sharedAccounts := []*string{} + visibility := string(image.Visibility) + + // Filter out public images + if strings.ToUpper(visibility) != "PUBLIC" { + imagePerms, err := svc.DescribeImagePermissions(ctx, &appstream.DescribeImagePermissionsInput{ + Name: image.Name, + }) + + if err != nil { + return nil, err + } + + for _, permission := range imagePerms.SharedImagePermissionsList { + sharedAccounts = append(sharedAccounts, permission.SharedAccountId) + } + + resources = append(resources, &AppStreamImage{ + svc: svc, + name: image.Name, + visibility: &visibility, + sharedAccounts: sharedAccounts, + }) + } + } } return resources, nil } type AppStreamImage struct { - svc *appstream.AppStream - name *string - visibility *string + svc *appstream.Client + name *string + visibility *string + sharedAccounts []*string } -func (f *AppStreamImage) Remove(_ context.Context) error { - _, err := f.svc.DeleteImage(&appstream.DeleteImageInput{ +func (f *AppStreamImage) Remove(ctx context.Context) error { + for _, account := range f.sharedAccounts { + _, err := f.svc.DeleteImagePermissions(ctx, &appstream.DeleteImagePermissionsInput{ + Name: f.name, + SharedAccountId: account, + }) + if err != nil { + fmt.Println("Error deleting image permissions", err) + return err + } + } + + _, err := f.svc.DeleteImage(ctx, &appstream.DeleteImageInput{ Name: f.name, }) @@ -76,3 +114,7 @@ func (f *AppStreamImage) Filter() error { } return nil } + +func (f *AppStreamImage) Properties() types.Properties { + return types.NewPropertiesFromStruct(f) +} diff --git a/resources/backup-vaults-access-policies.go b/resources/backup-vaults-access-policies.go index 2395a78f..315b9893 100644 --- a/resources/backup-vaults-access-policies.go +++ b/resources/backup-vaults-access-policies.go @@ -2,6 +2,8 @@ package resources import ( "context" + "fmt" + "strings" "github.com/ekristen/aws-nuke/v3/pkg/nuke" "github.com/ekristen/libnuke/pkg/registry" @@ -133,3 +135,10 @@ func (b *BackupVaultAccessPolicy) Remove(_ context.Context) error { func (b *BackupVaultAccessPolicy) String() string { return b.backupVaultName } + +func (b *BackupVaultAccessPolicy) Filter() error { + if strings.HasPrefix(b.backupVaultName, "aws/efs/automatic-backup-vault") { + return fmt.Errorf("cannot delete EFS automatic backups backup policy") + } + return nil +} diff --git a/resources/cloudcontrol.go b/resources/cloudcontrol.go index 7d95560e..7f789711 100644 --- a/resources/cloudcontrol.go +++ b/resources/cloudcontrol.go @@ -47,7 +47,6 @@ func init() { registerCloudControl("AWS::Timestream::ScheduledQuery") registerCloudControl("AWS::Timestream::Table") registerCloudControl("AWS::Transfer::Workflow") - registerCloudControl("AWS::NetworkFirewall::Firewall") registerCloudControl("AWS::NetworkFirewall::FirewallPolicy") registerCloudControl("AWS::NetworkFirewall::RuleGroup") } diff --git a/resources/cloudformation-stack.go b/resources/cloudformation-stack.go index 5ed09049..66840a7b 100644 --- a/resources/cloudformation-stack.go +++ b/resources/cloudformation-stack.go @@ -15,6 +15,9 @@ import ( "github.com/aws/aws-sdk-go/service/cloudformation" "github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface" + "github.com/aws/aws-sdk-go-v2/service/iam" + iamtypes "github.com/aws/aws-sdk-go-v2/service/iam/types" + liberrors "github.com/ekristen/libnuke/pkg/errors" "github.com/ekristen/libnuke/pkg/registry" "github.com/ekristen/libnuke/pkg/resource" @@ -36,6 +39,7 @@ func init() { Lister: &CloudFormationStackLister{}, Settings: []string{ "DisableDeletionProtection", + "CreateRoleToDeleteStack", }, }) } @@ -46,6 +50,7 @@ func (l *CloudFormationStackLister) List(_ context.Context, o interface{}) ([]re opts := o.(*nuke.ListerOpts) svc := cloudformation.New(opts.Session) + iamSvc := iam.NewFromConfig(*opts.Config) params := &cloudformation.DescribeStacksInput{} resources := make([]resource.Resource, 0) @@ -56,11 +61,26 @@ func (l *CloudFormationStackLister) List(_ context.Context, o interface{}) ([]re return nil, err } for _, stack := range resp.Stacks { - resources = append(resources, &CloudFormationStack{ + newResource := &CloudFormationStack{ svc: svc, - stack: stack, + iamSvc: iamSvc, + logger: opts.Logger, maxDeleteAttempts: CloudformationMaxDeleteAttempt, - }) + Name: stack.StackName, + Status: stack.StackStatus, + description: stack.Description, + parentID: stack.ParentId, + roleARN: stack.RoleARN, + CreationTime: stack.CreationTime, + LastUpdatedTime: stack.LastUpdatedTime, + Tags: stack.Tags, + } + + if newResource.LastUpdatedTime == nil { + newResource.LastUpdatedTime = newResource.CreationTime + } + + resources = append(resources, newResource) } if resp.NextToken == nil { @@ -75,62 +95,126 @@ func (l *CloudFormationStackLister) List(_ context.Context, o interface{}) ([]re type CloudFormationStack struct { svc cloudformationiface.CloudFormationAPI - stack *cloudformation.Stack - maxDeleteAttempts int + iamSvc *iam.Client settings *settings.Setting + logger *logrus.Entry + Name *string + Status *string + CreationTime *time.Time + LastUpdatedTime *time.Time + Tags []*cloudformation.Tag + description *string + parentID *string + roleARN *string + maxDeleteAttempts int + roleCreated bool + roleName string } -func (cfs *CloudFormationStack) Filter() error { - if ptr.ToString(cfs.stack.Description) == "DO NOT MODIFY THIS STACK! This stack is managed by Config Conformance Packs." { +func (r *CloudFormationStack) Filter() error { + if ptr.ToString(r.description) == "DO NOT MODIFY THIS STACK! This stack is managed by Config Conformance Packs." { return fmt.Errorf("stack is managed by Config Conformance Packs") } return nil } -func (cfs *CloudFormationStack) Settings(setting *settings.Setting) { - cfs.settings = setting +func (r *CloudFormationStack) Settings(setting *settings.Setting) { + r.settings = setting } -func (cfs *CloudFormationStack) Remove(_ context.Context) error { - return cfs.removeWithAttempts(0) +func (r *CloudFormationStack) Remove(ctx context.Context) error { + return r.removeWithAttempts(ctx, 0) } -func (cfs *CloudFormationStack) removeWithAttempts(attempt int) error { - if err := cfs.doRemove(); err != nil { - // TODO: pass logrus in via ListerOpts so that it can be used here instead of global +func (r *CloudFormationStack) createRole(ctx context.Context) error { + roleParts := strings.Split(ptr.ToString(r.roleARN), "/") + _, err := r.iamSvc.CreateRole(ctx, &iam.CreateRoleInput{ + RoleName: ptr.String(roleParts[len(roleParts)-1]), + AssumeRolePolicyDocument: ptr.String(`{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "cloudformation.amazonaws.com" + }, + "Action": "sts:AssumeRole" + } + ] +}`), + Tags: []iamtypes.Tag{ + { + Key: ptr.String("Managed"), + Value: ptr.String("aws-nuke"), + }, + }, + }) + + r.roleCreated = true + r.roleName = roleParts[len(roleParts)-1] + + return err +} + +func (r *CloudFormationStack) removeRole(ctx context.Context) error { + if !r.roleCreated { + return nil + } + + _, err := r.iamSvc.DeleteRole(ctx, &iam.DeleteRoleInput{ + RoleName: ptr.String(r.roleName), + }) + return err +} - logrus.Errorf("CloudFormationStack stackName=%s attempt=%d maxAttempts=%d delete failed: %s", - *cfs.stack.StackName, attempt, cfs.maxDeleteAttempts, err.Error()) +func (r *CloudFormationStack) removeWithAttempts(ctx context.Context, attempt int) error { + if err := r.doRemove(); err != nil { + r.logger.Errorf("CloudFormationStack stackName=%s attempt=%d maxAttempts=%d delete failed: %s", + *r.Name, attempt, r.maxDeleteAttempts, err.Error()) var awsErr awserr.Error ok := errors.As(err, &awsErr) - if ok && awsErr.Code() == "ValidationError" && - awsErr.Message() == "Stack ["+*cfs.stack.StackName+"] cannot be deleted while TerminationProtection is enabled" { - // check if the setting for the resource is set to allow deletion protection to be disabled - if cfs.settings.GetBool("DisableDeletionProtection") { - logrus.Infof("CloudFormationStack stackName=%s attempt=%d maxAttempts=%d updating termination protection", - *cfs.stack.StackName, attempt, cfs.maxDeleteAttempts) - _, err = cfs.svc.UpdateTerminationProtection(&cloudformation.UpdateTerminationProtectionInput{ - EnableTerminationProtection: aws.Bool(false), - StackName: cfs.stack.StackName, - }) - if err != nil { + if ok && awsErr.Code() == "ValidationError" { + if awsErr.Message() == fmt.Sprintf("Role %s is invalid or cannot be assumed", *r.roleARN) { + if r.settings.GetBool("CreateRoleToDeleteStack") { + r.logger.Infof("CloudFormationStack stackName=%s attempt=%d maxAttempts=%d creating role to delete stack", + *r.Name, attempt, r.maxDeleteAttempts) + if err := r.createRole(ctx); err != nil { + return err + } + } else { + r.logger.Warnf("CloudFormationStack stackName=%s attempt=%d maxAttempts=%d set feature flag to create role to delete stack", + *r.Name, attempt, r.maxDeleteAttempts) + return err + } + } else if awsErr.Message() == fmt.Sprintf("Stack [%s] cannot be deleted while TerminationProtection is enabled", *r.Name) { + // check if the setting for the resource is set to allow deletion protection to be disabled + if r.settings.GetBool("DisableDeletionProtection") { + r.logger.Infof("CloudFormationStack stackName=%s attempt=%d maxAttempts=%d updating termination protection", + *r.Name, attempt, r.maxDeleteAttempts) + _, err = r.svc.UpdateTerminationProtection(&cloudformation.UpdateTerminationProtectionInput{ + EnableTerminationProtection: aws.Bool(false), + StackName: r.Name, + }) + if err != nil { + return err + } + } else { + r.logger.Warnf("CloudFormationStack stackName=%s attempt=%d maxAttempts=%d set feature flag to disable deletion protection", + *r.Name, attempt, r.maxDeleteAttempts) return err } - } else { - logrus.Warnf("CloudFormationStack stackName=%s attempt=%d maxAttempts=%d set feature flag to disable deletion protection", - *cfs.stack.StackName, attempt, cfs.maxDeleteAttempts) - return err } } - if attempt >= cfs.maxDeleteAttempts { + + if attempt >= r.maxDeleteAttempts { return errors.New("CFS might not be deleted after this run") } else { - return cfs.removeWithAttempts(attempt + 1) + return r.removeWithAttempts(ctx, attempt+1) } - } else { - return nil } + + return r.removeRole(ctx) } func GetParentStack(svc cloudformationiface.CloudFormationAPI, stackID string) (*cloudformation.Stack, error) { @@ -148,9 +232,9 @@ func GetParentStack(svc cloudformationiface.CloudFormationAPI, stackID string) ( return nil, nil //nolint:nilnil } -func (cfs *CloudFormationStack) doRemove() error { //nolint:gocyclo - if cfs.stack.ParentId != nil { - p, err := GetParentStack(cfs.svc, *cfs.stack.ParentId) +func (r *CloudFormationStack) doRemove() error { //nolint:gocyclo + if r.parentID != nil { + p, err := GetParentStack(r.svc, *r.parentID) if err != nil { return err } @@ -160,14 +244,14 @@ func (cfs *CloudFormationStack) doRemove() error { //nolint:gocyclo } } - o, err := cfs.svc.DescribeStacks(&cloudformation.DescribeStacksInput{ - StackName: cfs.stack.StackName, + o, err := r.svc.DescribeStacks(&cloudformation.DescribeStacksInput{ + StackName: r.Name, }) if err != nil { var awsErr awserr.Error if errors.As(err, &awsErr) { if awsErr.Code() == "ValidationFailed" && strings.HasSuffix(awsErr.Message(), " does not exist") { - logrus.Infof("CloudFormationStack stackName=%s no longer exists", *cfs.stack.StackName) + r.logger.Infof("CloudFormationStack stackName=%s no longer exists", *r.Name) return nil } } @@ -179,16 +263,16 @@ func (cfs *CloudFormationStack) doRemove() error { //nolint:gocyclo // stack already deleted, no need to re-delete return nil } else if *stack.StackStatus == cloudformation.StackStatusDeleteInProgress { - logrus.Infof("CloudFormationStack stackName=%s delete in progress. Waiting", *cfs.stack.StackName) - return cfs.svc.WaitUntilStackDeleteComplete(&cloudformation.DescribeStacksInput{ - StackName: cfs.stack.StackName, + r.logger.Infof("CloudFormationStack stackName=%s delete in progress. Waiting", *r.Name) + return r.svc.WaitUntilStackDeleteComplete(&cloudformation.DescribeStacksInput{ + StackName: r.Name, }) } else if *stack.StackStatus == cloudformation.StackStatusDeleteFailed { - logrus.Infof("CloudFormationStack stackName=%s delete failed. Attempting to retain and delete stack", *cfs.stack.StackName) + r.logger.Infof("CloudFormationStack stackName=%s delete failed. Attempting to retain and delete stack", *r.Name) // This means the CFS has undetectable resources. // In order to move on with nuking, we retain them in the deletion. - retainableResources, err := cfs.svc.ListStackResources(&cloudformation.ListStackResourcesInput{ - StackName: cfs.stack.StackName, + retainableResources, err := r.svc.ListStackResources(&cloudformation.ListStackResourcesInput{ + StackName: r.Name, }) if err != nil { return err @@ -202,25 +286,25 @@ func (cfs *CloudFormationStack) doRemove() error { //nolint:gocyclo } } - _, err = cfs.svc.DeleteStack(&cloudformation.DeleteStackInput{ - StackName: cfs.stack.StackName, + if _, err = r.svc.DeleteStack(&cloudformation.DeleteStackInput{ + StackName: r.Name, RetainResources: retain, - }) - if err != nil { + }); err != nil { return err } - return cfs.svc.WaitUntilStackDeleteComplete(&cloudformation.DescribeStacksInput{ - StackName: cfs.stack.StackName, + + return r.svc.WaitUntilStackDeleteComplete(&cloudformation.DescribeStacksInput{ + StackName: r.Name, }) } else { - if err := cfs.waitForStackToStabilize(*stack.StackStatus); err != nil { + if err := r.waitForStackToStabilize(*stack.StackStatus); err != nil { return err - } else if _, err := cfs.svc.DeleteStack(&cloudformation.DeleteStackInput{ - StackName: cfs.stack.StackName, + } else if _, err := r.svc.DeleteStack(&cloudformation.DeleteStackInput{ + StackName: r.Name, }); err != nil { return err - } else if err := cfs.svc.WaitUntilStackDeleteComplete(&cloudformation.DescribeStacksInput{ - StackName: cfs.stack.StackName, + } else if err := r.svc.WaitUntilStackDeleteComplete(&cloudformation.DescribeStacksInput{ + StackName: r.Name, }); err != nil { return err } else { @@ -228,45 +312,33 @@ func (cfs *CloudFormationStack) doRemove() error { //nolint:gocyclo } } } -func (cfs *CloudFormationStack) waitForStackToStabilize(currentStatus string) error { + +func (r *CloudFormationStack) waitForStackToStabilize(currentStatus string) error { switch currentStatus { case cloudformation.StackStatusUpdateInProgress, cloudformation.StackStatusUpdateRollbackCompleteCleanupInProgress, cloudformation.StackStatusUpdateRollbackInProgress: - logrus.Infof("CloudFormationStack stackName=%s update in progress. Waiting to stabalize", *cfs.stack.StackName) + r.logger.Infof("CloudFormationStack stackName=%s update in progress. Waiting to stabalize", *r.Name) - return cfs.svc.WaitUntilStackUpdateComplete(&cloudformation.DescribeStacksInput{ - StackName: cfs.stack.StackName, + return r.svc.WaitUntilStackUpdateComplete(&cloudformation.DescribeStacksInput{ + StackName: r.Name, }) case cloudformation.StackStatusCreateInProgress, cloudformation.StackStatusRollbackInProgress: - logrus.Infof("CloudFormationStack stackName=%s create in progress. Waiting to stabalize", *cfs.stack.StackName) + r.logger.Infof("CloudFormationStack stackName=%s create in progress. Waiting to stabalize", *r.Name) - return cfs.svc.WaitUntilStackCreateComplete(&cloudformation.DescribeStacksInput{ - StackName: cfs.stack.StackName, + return r.svc.WaitUntilStackCreateComplete(&cloudformation.DescribeStacksInput{ + StackName: r.Name, }) default: return nil } } -func (cfs *CloudFormationStack) Properties() types.Properties { - properties := types.NewProperties() - properties.Set("Name", cfs.stack.StackName) - properties.Set("CreationTime", cfs.stack.CreationTime.Format(time.RFC3339)) - if cfs.stack.LastUpdatedTime == nil { - properties.Set("LastUpdatedTime", cfs.stack.CreationTime.Format(time.RFC3339)) - } else { - properties.Set("LastUpdatedTime", cfs.stack.LastUpdatedTime.Format(time.RFC3339)) - } - - for _, tagValue := range cfs.stack.Tags { - properties.SetTag(tagValue.Key, tagValue.Value) - } - - return properties +func (r *CloudFormationStack) Properties() types.Properties { + return types.NewPropertiesFromStruct(r) } -func (cfs *CloudFormationStack) String() string { - return *cfs.stack.StackName +func (r *CloudFormationStack) String() string { + return *r.Name } diff --git a/resources/cloudformation-stack_test.go b/resources/cloudformation-stack_test.go index 9b7bc82a..9eba55a7 100644 --- a/resources/cloudformation-stack_test.go +++ b/resources/cloudformation-stack_test.go @@ -2,13 +2,14 @@ package resources import ( "context" - "testing" + "time" "github.com/golang/mock/gomock" + "github.com/gotidy/ptr" + "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/cloudformation" @@ -17,6 +18,35 @@ import ( "github.com/ekristen/aws-nuke/v3/mocks/mock_cloudformationiface" ) +func TestCloudformationStack_Properties(t *testing.T) { + a := assert.New(t) + + now := time.Now() + + stack := CloudFormationStack{ + Name: ptr.String("foobar"), + Status: ptr.String(cloudformation.StackStatusCreateComplete), + CreationTime: ptr.Time(now), + LastUpdatedTime: ptr.Time(now), + Tags: []*cloudformation.Tag{ + { + Key: ptr.String("Name"), + Value: ptr.String("foobar"), + }, + }, + } + + props := stack.Properties() + + a.Equal("foobar", props.Get("Name")) + a.Equal(cloudformation.StackStatusCreateComplete, props.Get("Status")) + a.Equal(now.Format(time.RFC3339), props.Get("CreationTime")) + a.Equal(now.Format(time.RFC3339), props.Get("LastUpdatedTime")) + a.Equal("foobar", props.Get("tag:Name")) + + a.Equal("foobar", stack.String()) +} + func TestCloudformationStack_Remove_StackAlreadyDeleted(t *testing.T) { a := assert.New(t) ctrl := gomock.NewController(t) @@ -25,21 +55,20 @@ func TestCloudformationStack_Remove_StackAlreadyDeleted(t *testing.T) { mockCloudformation := mock_cloudformationiface.NewMockCloudFormationAPI(ctrl) stack := CloudFormationStack{ - svc: mockCloudformation, - stack: &cloudformation.Stack{ - StackName: aws.String("foobar"), - }, + svc: mockCloudformation, + logger: logrus.NewEntry(logrus.StandardLogger()), + Name: ptr.String("foobar"), settings: &libsettings.Setting{ "DisableDeletionProtection": true, }, } mockCloudformation.EXPECT().DescribeStacks(gomock.Eq(&cloudformation.DescribeStacksInput{ - StackName: aws.String("foobar"), + StackName: ptr.String("foobar"), })).Return(&cloudformation.DescribeStacksOutput{ Stacks: []*cloudformation.Stack{ { - StackStatus: aws.String(cloudformation.StackStatusDeleteComplete), + StackStatus: ptr.String(cloudformation.StackStatusDeleteComplete), }, }, }, nil) @@ -56,17 +85,16 @@ func TestCloudformationStack_Remove_StackDoesNotExist(t *testing.T) { mockCloudformation := mock_cloudformationiface.NewMockCloudFormationAPI(ctrl) stack := CloudFormationStack{ - svc: mockCloudformation, - stack: &cloudformation.Stack{ - StackName: aws.String("foobar"), - }, + svc: mockCloudformation, + logger: logrus.NewEntry(logrus.StandardLogger()), + Name: ptr.String("foobar"), settings: &libsettings.Setting{ "DisableDeletionProtection": true, }, } mockCloudformation.EXPECT().DescribeStacks(gomock.Eq(&cloudformation.DescribeStacksInput{ - StackName: aws.String("foobar"), + StackName: ptr.String("foobar"), })).Return(nil, awserr.New("ValidationFailed", "Stack with id foobar does not exist", nil)) err := stack.Remove(context.TODO()) @@ -81,10 +109,9 @@ func TestCloudformationStack_Remove_DeleteFailed(t *testing.T) { mockCloudformation := mock_cloudformationiface.NewMockCloudFormationAPI(ctrl) stack := CloudFormationStack{ - svc: mockCloudformation, - stack: &cloudformation.Stack{ - StackName: aws.String("foobar"), - }, + svc: mockCloudformation, + logger: logrus.NewEntry(logrus.StandardLogger()), + Name: ptr.String("foobar"), settings: &libsettings.Setting{ "DisableDeletionProtection": true, }, @@ -92,36 +119,36 @@ func TestCloudformationStack_Remove_DeleteFailed(t *testing.T) { gomock.InOrder( mockCloudformation.EXPECT().DescribeStacks(gomock.Eq(&cloudformation.DescribeStacksInput{ - StackName: aws.String("foobar"), + StackName: ptr.String("foobar"), })).Return(&cloudformation.DescribeStacksOutput{ Stacks: []*cloudformation.Stack{ { - StackStatus: aws.String(cloudformation.StackStatusDeleteFailed), + StackStatus: ptr.String(cloudformation.StackStatusDeleteFailed), }, }, }, nil), mockCloudformation.EXPECT().ListStackResources(gomock.Eq(&cloudformation.ListStackResourcesInput{ - StackName: aws.String("foobar"), + StackName: ptr.String("foobar"), })).Return(&cloudformation.ListStackResourcesOutput{ StackResourceSummaries: []*cloudformation.StackResourceSummary{ { - ResourceStatus: aws.String(cloudformation.ResourceStatusDeleteComplete), - LogicalResourceId: aws.String("fooDeleteComplete"), + ResourceStatus: ptr.String(cloudformation.ResourceStatusDeleteComplete), + LogicalResourceId: ptr.String("fooDeleteComplete"), }, { - ResourceStatus: aws.String(cloudformation.ResourceStatusDeleteFailed), - LogicalResourceId: aws.String("fooDeleteFailed"), + ResourceStatus: ptr.String(cloudformation.ResourceStatusDeleteFailed), + LogicalResourceId: ptr.String("fooDeleteFailed"), }, }, }, nil), mockCloudformation.EXPECT().DeleteStack(gomock.Eq(&cloudformation.DeleteStackInput{ - StackName: aws.String("foobar"), + StackName: ptr.String("foobar"), RetainResources: []*string{ - aws.String("fooDeleteFailed"), + ptr.String("fooDeleteFailed"), }, })).Return(nil, nil), mockCloudformation.EXPECT().WaitUntilStackDeleteComplete(gomock.Eq(&cloudformation.DescribeStacksInput{ - StackName: aws.String("foobar"), + StackName: ptr.String("foobar"), })).Return(nil), ) @@ -138,10 +165,9 @@ func TestCloudformationStack_Remove_DeleteInProgress(t *testing.T) { mockCloudformation := mock_cloudformationiface.NewMockCloudFormationAPI(ctrl) stack := CloudFormationStack{ - svc: mockCloudformation, - stack: &cloudformation.Stack{ - StackName: aws.String("foobar"), - }, + svc: mockCloudformation, + logger: logrus.NewEntry(logrus.StandardLogger()), + Name: ptr.String("foobar"), settings: &libsettings.Setting{ "DisableDeletionProtection": true, }, @@ -149,17 +175,17 @@ func TestCloudformationStack_Remove_DeleteInProgress(t *testing.T) { gomock.InOrder( mockCloudformation.EXPECT().DescribeStacks(gomock.Eq(&cloudformation.DescribeStacksInput{ - StackName: aws.String("foobar"), + StackName: ptr.String("foobar"), })).Return(&cloudformation.DescribeStacksOutput{ Stacks: []*cloudformation.Stack{ { - StackStatus: aws.String(cloudformation.StackStatusDeleteInProgress), + StackStatus: ptr.String(cloudformation.StackStatusDeleteInProgress), }, }, }, nil), mockCloudformation.EXPECT().WaitUntilStackDeleteComplete(gomock.Eq(&cloudformation.DescribeStacksInput{ - StackName: aws.String("foobar"), + StackName: ptr.String("foobar"), })).Return(nil), ) @@ -188,10 +214,9 @@ func TestCloudformationStack_Remove_Stack_InCompletedStatus(t *testing.T) { mockCloudformation := mock_cloudformationiface.NewMockCloudFormationAPI(ctrl) stack := CloudFormationStack{ - svc: mockCloudformation, - stack: &cloudformation.Stack{ - StackName: aws.String("foobar"), - }, + svc: mockCloudformation, + logger: logrus.NewEntry(logrus.StandardLogger()), + Name: ptr.String("foobar"), settings: &libsettings.Setting{ "DisableDeletionProtection": true, }, @@ -199,21 +224,21 @@ func TestCloudformationStack_Remove_Stack_InCompletedStatus(t *testing.T) { gomock.InOrder( mockCloudformation.EXPECT().DescribeStacks(gomock.Eq(&cloudformation.DescribeStacksInput{ - StackName: aws.String("foobar"), + StackName: ptr.String("foobar"), })).Return(&cloudformation.DescribeStacksOutput{ Stacks: []*cloudformation.Stack{ { - StackStatus: aws.String(stackStatus), + StackStatus: ptr.String(stackStatus), }, }, }, nil), mockCloudformation.EXPECT().DeleteStack(gomock.Eq(&cloudformation.DeleteStackInput{ - StackName: aws.String("foobar"), + StackName: ptr.String("foobar"), })).Return(nil, nil), mockCloudformation.EXPECT().WaitUntilStackDeleteComplete(gomock.Eq(&cloudformation.DescribeStacksInput{ - StackName: aws.String("foobar"), + StackName: ptr.String("foobar"), })).Return(nil), ) @@ -238,10 +263,9 @@ func TestCloudformationStack_Remove_Stack_CreateInProgress(t *testing.T) { mockCloudformation := mock_cloudformationiface.NewMockCloudFormationAPI(ctrl) stack := CloudFormationStack{ - svc: mockCloudformation, - stack: &cloudformation.Stack{ - StackName: aws.String("foobar"), - }, + svc: mockCloudformation, + logger: logrus.NewEntry(logrus.StandardLogger()), + Name: ptr.String("foobar"), settings: &libsettings.Setting{ "DisableDeletionProtection": true, }, @@ -249,25 +273,25 @@ func TestCloudformationStack_Remove_Stack_CreateInProgress(t *testing.T) { gomock.InOrder( mockCloudformation.EXPECT().DescribeStacks(gomock.Eq(&cloudformation.DescribeStacksInput{ - StackName: aws.String("foobar"), + StackName: ptr.String("foobar"), })).Return(&cloudformation.DescribeStacksOutput{ Stacks: []*cloudformation.Stack{ { - StackStatus: aws.String(stackStatus), + StackStatus: ptr.String(stackStatus), }, }, }, nil), mockCloudformation.EXPECT().WaitUntilStackCreateComplete(gomock.Eq(&cloudformation.DescribeStacksInput{ - StackName: aws.String("foobar"), + StackName: ptr.String("foobar"), })).Return(nil), mockCloudformation.EXPECT().DeleteStack(gomock.Eq(&cloudformation.DeleteStackInput{ - StackName: aws.String("foobar"), + StackName: ptr.String("foobar"), })).Return(nil, nil), mockCloudformation.EXPECT().WaitUntilStackDeleteComplete(gomock.Eq(&cloudformation.DescribeStacksInput{ - StackName: aws.String("foobar"), + StackName: ptr.String("foobar"), })).Return(nil), ) @@ -293,10 +317,9 @@ func TestCloudformationStack_Remove_Stack_UpdateInProgress(t *testing.T) { mockCloudformation := mock_cloudformationiface.NewMockCloudFormationAPI(ctrl) stack := CloudFormationStack{ - svc: mockCloudformation, - stack: &cloudformation.Stack{ - StackName: aws.String("foobar"), - }, + svc: mockCloudformation, + logger: logrus.NewEntry(logrus.StandardLogger()), + Name: ptr.String("foobar"), settings: &libsettings.Setting{ "DisableDeletionProtection": true, }, @@ -304,25 +327,25 @@ func TestCloudformationStack_Remove_Stack_UpdateInProgress(t *testing.T) { gomock.InOrder( mockCloudformation.EXPECT().DescribeStacks(gomock.Eq(&cloudformation.DescribeStacksInput{ - StackName: aws.String("foobar"), + StackName: ptr.String("foobar"), })).Return(&cloudformation.DescribeStacksOutput{ Stacks: []*cloudformation.Stack{ { - StackStatus: aws.String(stackStatus), + StackStatus: ptr.String(stackStatus), }, }, }, nil), mockCloudformation.EXPECT().WaitUntilStackUpdateComplete(gomock.Eq(&cloudformation.DescribeStacksInput{ - StackName: aws.String("foobar"), + StackName: ptr.String("foobar"), })).Return(nil), mockCloudformation.EXPECT().DeleteStack(gomock.Eq(&cloudformation.DeleteStackInput{ - StackName: aws.String("foobar"), + StackName: ptr.String("foobar"), })).Return(nil, nil), mockCloudformation.EXPECT().WaitUntilStackDeleteComplete(gomock.Eq(&cloudformation.DescribeStacksInput{ - StackName: aws.String("foobar"), + StackName: ptr.String("foobar"), })).Return(nil), ) diff --git a/resources/cloudwatch-alarms.go b/resources/cloudwatch-alarm.go similarity index 61% rename from resources/cloudwatch-alarms.go rename to resources/cloudwatch-alarm.go index a7d9bc22..127e226f 100644 --- a/resources/cloudwatch-alarms.go +++ b/resources/cloudwatch-alarm.go @@ -3,6 +3,8 @@ package resources import ( "context" + "github.com/gotidy/ptr" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/cloudwatch" @@ -33,6 +35,10 @@ func (l *CloudWatchAlarmLister) List(_ context.Context, o interface{}) ([]resour resources := make([]resource.Resource, 0) params := &cloudwatch.DescribeAlarmsInput{ + AlarmTypes: []*string{ + ptr.String(cloudwatch.AlarmTypeCompositeAlarm), + ptr.String(cloudwatch.AlarmTypeMetricAlarm), + }, MaxRecords: aws.Int64(100), } @@ -48,9 +54,23 @@ func (l *CloudWatchAlarmLister) List(_ context.Context, o interface{}) ([]resour return nil, err } resources = append(resources, &CloudWatchAlarm{ - svc: svc, - alarmName: metricAlarm.AlarmName, - tags: tags, + svc: svc, + Name: metricAlarm.AlarmName, + Type: ptr.String(cloudwatch.AlarmTypeMetricAlarm), + Tags: tags, + }) + } + + for _, compositeAlarm := range output.CompositeAlarms { + tags, err := GetAlarmTags(svc, compositeAlarm.AlarmArn) + if err != nil { + return nil, err + } + resources = append(resources, &CloudWatchAlarm{ + svc: svc, + Name: compositeAlarm.AlarmName, + Type: ptr.String(cloudwatch.AlarmTypeCompositeAlarm), + Tags: tags, }) } @@ -74,29 +94,24 @@ func GetAlarmTags(svc *cloudwatch.CloudWatch, arn *string) ([]*cloudwatch.Tag, e } type CloudWatchAlarm struct { - svc *cloudwatch.CloudWatch - alarmName *string - tags []*cloudwatch.Tag + svc *cloudwatch.CloudWatch + Name *string + Type *string + Tags []*cloudwatch.Tag } -func (f *CloudWatchAlarm) Remove(_ context.Context) error { - _, err := f.svc.DeleteAlarms(&cloudwatch.DeleteAlarmsInput{ - AlarmNames: []*string{f.alarmName}, +func (r *CloudWatchAlarm) Remove(_ context.Context) error { + _, err := r.svc.DeleteAlarms(&cloudwatch.DeleteAlarmsInput{ + AlarmNames: []*string{r.Name}, }) return err } -func (f *CloudWatchAlarm) Properties() types.Properties { - properties := types.NewProperties() - properties.Set("Name", f.alarmName) - - for _, tag := range f.tags { - properties.SetTag(tag.Key, tag.Value) - } - return properties +func (r *CloudWatchAlarm) Properties() types.Properties { + return types.NewPropertiesFromStruct(r) } -func (f *CloudWatchAlarm) String() string { - return *f.alarmName +func (r *CloudWatchAlarm) String() string { + return *r.Name } diff --git a/resources/cloudwatchevents-targets.go b/resources/cloudwatchevents-target.go similarity index 66% rename from resources/cloudwatchevents-targets.go rename to resources/cloudwatchevents-target.go index 932b4368..c8918b66 100644 --- a/resources/cloudwatchevents-targets.go +++ b/resources/cloudwatchevents-target.go @@ -2,14 +2,15 @@ package resources import ( "context" - "fmt" - "github.com/aws/aws-sdk-go/aws" + "github.com/gotidy/ptr" + "github.com/aws/aws-sdk-go/service/cloudwatchevents" "github.com/ekristen/libnuke/pkg/registry" "github.com/ekristen/libnuke/pkg/resource" + "github.com/ekristen/libnuke/pkg/types" "github.com/ekristen/aws-nuke/v3/pkg/nuke" ) @@ -56,9 +57,9 @@ func (l *CloudWatchEventsTargetLister) List(_ context.Context, o interface{}) ([ for _, target := range targetResp.Targets { resources = append(resources, &CloudWatchEventsTarget{ svc: svc, - ruleName: rule.Name, - targetID: target.Id, - busName: bus.Name, + Name: rule.Name, + TargetID: target.Id, + BusName: bus.Name, }) } } @@ -69,24 +70,27 @@ func (l *CloudWatchEventsTargetLister) List(_ context.Context, o interface{}) ([ type CloudWatchEventsTarget struct { svc *cloudwatchevents.CloudWatchEvents - targetID *string - ruleName *string - busName *string + TargetID *string `description:"The ID of the target for the rule"` + Name *string `description:"The name of the rule"` + BusName *string `description:"The name of the event bus the rule applies to"` } -func (target *CloudWatchEventsTarget) Remove(_ context.Context) error { - ids := []*string{target.targetID} - _, err := target.svc.RemoveTargets(&cloudwatchevents.RemoveTargetsInput{ +func (r *CloudWatchEventsTarget) Remove(_ context.Context) error { + ids := []*string{r.TargetID} + _, err := r.svc.RemoveTargets(&cloudwatchevents.RemoveTargetsInput{ Ids: ids, - Rule: target.ruleName, - EventBusName: target.busName, - Force: aws.Bool(true), + Rule: r.Name, + EventBusName: r.BusName, + Force: ptr.Bool(true), }) return err } -func (target *CloudWatchEventsTarget) String() string { +func (r *CloudWatchEventsTarget) String() string { // TODO: change this to IAM format rule -> target and mark as breaking change for filters - // TODO: add properties for rule and target separately - return fmt.Sprintf("Rule: %s Target ID: %s", *target.ruleName, *target.targetID) + return fmt.Sprintf("Rule: %s Target ID: %s", *r.Name, *r.TargetID) +} + +func (r *CloudWatchEventsTarget) Properties() types.Properties { + return types.NewPropertiesFromStruct(r) } diff --git a/resources/cloudwatchlogs-loggroups.go b/resources/cloudwatchlogs-loggroup.go similarity index 62% rename from resources/cloudwatchlogs-loggroups.go rename to resources/cloudwatchlogs-loggroup.go index eae192be..c4cfcb69 100644 --- a/resources/cloudwatchlogs-loggroups.go +++ b/resources/cloudwatchlogs-loggroup.go @@ -5,9 +5,9 @@ import ( "strings" "time" + "github.com/gotidy/ptr" "go.uber.org/ratelimit" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/cloudwatchlogs" "github.com/ekristen/libnuke/pkg/registry" @@ -46,7 +46,7 @@ func (l *CloudWatchLogsLogGroupLister) List(_ context.Context, o interface{}) ([ streamRl := ratelimit.New(15) params := &cloudwatchlogs.DescribeLogGroupsInput{ - Limit: aws.Int64(50), + Limit: ptr.Int64(50), } for { @@ -72,9 +72,9 @@ func (l *CloudWatchLogsLogGroupLister) List(_ context.Context, o interface{}) ([ // get last event ingestion time lsResp, err := svc.DescribeLogStreams(&cloudwatchlogs.DescribeLogStreamsInput{ LogGroupName: logGroup.LogGroupName, - OrderBy: aws.String("LastEventTime"), - Limit: aws.Int64(1), - Descending: aws.Bool(true), + OrderBy: ptr.String("LastEventTime"), + Limit: ptr.Int64(1), + Descending: ptr.Bool(true), }) if err != nil { return nil, err @@ -87,14 +87,21 @@ func (l *CloudWatchLogsLogGroupLister) List(_ context.Context, o interface{}) ([ lastEvent = time.Unix(*logGroup.CreationTime/1000, 0) } + var retentionInDays int64 + if logGroup.RetentionInDays != nil { + retentionInDays = ptr.ToInt64(logGroup.RetentionInDays) + } + resources = append(resources, &CloudWatchLogsLogGroup{ - svc: svc, - logGroup: logGroup, - lastEvent: lastEvent.Format(time.RFC3339), - tags: tagResp.Tags, + svc: svc, + Name: logGroup.LogGroupName, + CreatedTime: logGroup.CreationTime, + CreationTime: ptr.Time(time.Unix(*logGroup.CreationTime/1000, 0).UTC()), + LastEvent: ptr.Time(lastEvent), // TODO(v4): convert to UTC + RetentionInDays: retentionInDays, + Tags: tagResp.Tags, }) } - if output.NextToken == nil { break } @@ -106,32 +113,28 @@ func (l *CloudWatchLogsLogGroupLister) List(_ context.Context, o interface{}) ([ } type CloudWatchLogsLogGroup struct { - svc *cloudwatchlogs.CloudWatchLogs - logGroup *cloudwatchlogs.LogGroup - lastEvent string - tags map[string]*string + svc *cloudwatchlogs.CloudWatchLogs + Name *string `description:"The name of the log group"` + CreatedTime *int64 `description:"The creation time of the log group in unix timestamp format"` + CreationTime *time.Time `description:"The creation time of the log group in RFC3339 format"` + LastEvent *time.Time `description:"The last event time of the log group in RFC3339 format"` + RetentionInDays int64 `description:"The number of days to retain log events in the log group"` + Tags map[string]*string } -func (f *CloudWatchLogsLogGroup) Remove(_ context.Context) error { - _, err := f.svc.DeleteLogGroup(&cloudwatchlogs.DeleteLogGroupInput{ - LogGroupName: f.logGroup.LogGroupName, +func (r *CloudWatchLogsLogGroup) Remove(_ context.Context) error { + _, err := r.svc.DeleteLogGroup(&cloudwatchlogs.DeleteLogGroupInput{ + LogGroupName: r.Name, }) return err } -func (f *CloudWatchLogsLogGroup) String() string { - return *f.logGroup.LogGroupName +func (r *CloudWatchLogsLogGroup) String() string { + return *r.Name } -func (f *CloudWatchLogsLogGroup) Properties() types.Properties { - properties := types.NewProperties(). - Set("logGroupName", f.logGroup.LogGroupName). - Set("CreatedTime", f.logGroup.CreationTime). - Set("LastEvent", f.lastEvent) - - for k, v := range f.tags { - properties.SetTag(&k, v) - } - return properties +func (r *CloudWatchLogsLogGroup) Properties() types.Properties { + return types.NewPropertiesFromStruct(r). + Set("logGroupName", r.Name) // TODO(v4): remove this property } diff --git a/resources/cloudwatchlogs-loggroup_test.go b/resources/cloudwatchlogs-loggroup_test.go new file mode 100644 index 00000000..e2d2e669 --- /dev/null +++ b/resources/cloudwatchlogs-loggroup_test.go @@ -0,0 +1,34 @@ +package resources + +import ( + "strconv" + "testing" + "time" + + "github.com/gotidy/ptr" + "github.com/stretchr/testify/assert" +) + +func TestCloudWatchLogsLogGroupProperties(t *testing.T) { + now := time.Now().UTC() + + r := &CloudWatchLogsLogGroup{ + Name: ptr.String("test-log-group"), + CreatedTime: ptr.Int64(now.Unix()), + CreationTime: ptr.Time(now), + LastEvent: ptr.Time(now), + RetentionInDays: 7, + Tags: map[string]*string{ + "Environment": ptr.String("production"), + }, + } + + properties := r.Properties() + assert.Equal(t, properties.Get("logGroupName"), "test-log-group") + assert.Equal(t, properties.Get("Name"), "test-log-group") + assert.Equal(t, properties.Get("CreatedTime"), strconv.Itoa(int(now.Unix()))) + assert.Equal(t, properties.Get("CreationTime"), now.Format(time.RFC3339)) + assert.Equal(t, properties.Get("LastEvent"), now.Format(time.RFC3339)) + assert.Equal(t, properties.Get("RetentionInDays"), "7") + assert.Equal(t, properties.Get("tag:Environment"), "production") +} diff --git a/resources/cognito-userpool.go b/resources/cognito-userpool.go index 33fd6e82..fd528d73 100644 --- a/resources/cognito-userpool.go +++ b/resources/cognito-userpool.go @@ -99,13 +99,22 @@ type CognitoUserPool struct { func (r *CognitoUserPool) Remove(_ context.Context) error { if r.settings.GetBool("DisableDeletionProtection") { - _, err := r.svc.UpdateUserPool(&cognitoidentityprovider.UpdateUserPoolInput{ - UserPoolId: r.ID, - DeletionProtection: ptr.String("INACTIVE"), + userPool, err := r.svc.DescribeUserPool(&cognitoidentityprovider.DescribeUserPoolInput{ + UserPoolId: r.ID, }) if err != nil { return err } + + _, updateErr := r.svc.UpdateUserPool(&cognitoidentityprovider.UpdateUserPoolInput{ + UserPoolId: r.ID, + DeletionProtection: ptr.String("INACTIVE"), + UserAttributeUpdateSettings: userPool.UserPool.UserAttributeUpdateSettings, + AutoVerifiedAttributes: userPool.UserPool.AutoVerifiedAttributes, + }) + if updateErr != nil { + return updateErr + } } _, err := r.svc.DeleteUserPool(&cognitoidentityprovider.DeleteUserPoolInput{ diff --git a/resources/cognito-userpool_mock_test.go b/resources/cognito-userpool_mock_test.go index fc0bfa57..32244630 100644 --- a/resources/cognito-userpool_mock_test.go +++ b/resources/cognito-userpool_mock_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/golang/mock/gomock" + "github.com/gotidy/ptr" "github.com/stretchr/testify/assert" "github.com/aws/aws-sdk-go/aws" @@ -85,9 +86,24 @@ func Test_Mock_CognitoUserPool_Remove_DeletionProtection(t *testing.T) { mockSvc := mock_cognitoidentityprovideriface.NewMockCognitoIdentityProviderAPI(ctrl) + mockSvc.EXPECT().DescribeUserPool(&cognitoidentityprovider.DescribeUserPoolInput{ + UserPoolId: aws.String("test-pool-id"), + }).Return(&cognitoidentityprovider.DescribeUserPoolOutput{ + UserPool: &cognitoidentityprovider.UserPoolType{ + UserAttributeUpdateSettings: &cognitoidentityprovider.UserAttributeUpdateSettingsType{ + AttributesRequireVerificationBeforeUpdate: []*string{ptr.String("email")}, + }, + AutoVerifiedAttributes: []*string{ptr.String("email")}, + }, + }, nil) + mockSvc.EXPECT().UpdateUserPool(&cognitoidentityprovider.UpdateUserPoolInput{ UserPoolId: aws.String("test-pool-id"), DeletionProtection: aws.String("INACTIVE"), + UserAttributeUpdateSettings: &cognitoidentityprovider.UserAttributeUpdateSettingsType{ + AttributesRequireVerificationBeforeUpdate: []*string{ptr.String("email")}, + }, + AutoVerifiedAttributes: []*string{ptr.String("email")}, }).Return(&cognitoidentityprovider.UpdateUserPoolOutput{}, nil) mockSvc.EXPECT().DeleteUserPool(&cognitoidentityprovider.DeleteUserPoolInput{ diff --git a/resources/ec2-tgw.go b/resources/ec2-tgw.go index 5d34be67..9e6f5a54 100644 --- a/resources/ec2-tgw.go +++ b/resources/ec2-tgw.go @@ -4,6 +4,8 @@ import ( "context" "fmt" + "github.com/gotidy/ptr" + "github.com/aws/aws-sdk-go/service/ec2" "github.com/ekristen/libnuke/pkg/registry" @@ -45,8 +47,12 @@ func (l *EC2TGWLister) List(_ context.Context, o interface{}) ([]resource.Resour for _, tgw := range resp.TransitGateways { resources = append(resources, &EC2TGW{ - svc: svc, - tgw: tgw, + svc: svc, + ID: tgw.TransitGatewayId, + OwnerID: tgw.OwnerId, + Tags: tgw.Tags, + State: tgw.State, + accountID: opts.AccountID, }) } @@ -63,16 +69,21 @@ func (l *EC2TGWLister) List(_ context.Context, o interface{}) ([]resource.Resour } type EC2TGW struct { - svc *ec2.EC2 - tgw *ec2.TransitGateway + svc *ec2.EC2 + ID *string `description:"The ID of the transit gateway."` + OwnerID *string `property:"name=OwnerId" description:"The ID of the AWS account that owns the transit gateway."` + State *string `description:"The state of the transit gateway."` + Tags []*ec2.Tag `description:"The tags associated with the transit gateway."` + + accountID *string } -func (e *EC2TGW) Remove(_ context.Context) error { +func (r *EC2TGW) Remove(_ context.Context) error { params := &ec2.DeleteTransitGatewayInput{ - TransitGatewayId: e.tgw.TransitGatewayId, + TransitGatewayId: r.ID, } - _, err := e.svc.DeleteTransitGateway(params) + _, err := r.svc.DeleteTransitGateway(params) if err != nil { return err } @@ -80,26 +91,21 @@ func (e *EC2TGW) Remove(_ context.Context) error { return nil } -func (e *EC2TGW) Filter() error { - if *e.tgw.State == awsutil.StateDeleted { +func (r *EC2TGW) Filter() error { + if ptr.ToString(r.State) == awsutil.StateDeleted { return fmt.Errorf("already deleted") } + if ptr.ToString(r.OwnerID) != ptr.ToString(r.accountID) { + return fmt.Errorf("not owned by account") + } return nil } -func (e *EC2TGW) Properties() types.Properties { - properties := types.NewProperties() - for _, tagValue := range e.tgw.Tags { - properties.SetTag(tagValue.Key, tagValue.Value) - } - properties. - Set("ID", e.tgw.TransitGatewayId). - Set("OwnerId", e.tgw.OwnerId) - - return properties +func (r *EC2TGW) Properties() types.Properties { + return types.NewPropertiesFromStruct(r) } -func (e *EC2TGW) String() string { - return *e.tgw.TransitGatewayId +func (r *EC2TGW) String() string { + return *r.ID } diff --git a/resources/ec2-tgw_test.go b/resources/ec2-tgw_test.go new file mode 100644 index 00000000..de51f4f4 --- /dev/null +++ b/resources/ec2-tgw_test.go @@ -0,0 +1,28 @@ +package resources + +import ( + "testing" + + "github.com/gotidy/ptr" + "github.com/stretchr/testify/assert" + + "github.com/aws/aws-sdk-go/service/ec2" +) + +func Test_EC2TGW_Properties(t *testing.T) { + tgw := &EC2TGW{ + ID: ptr.String("tgw-1234567890abcdef0"), + OwnerID: ptr.String("123456789012"), + Tags: []*ec2.Tag{ + { + Key: ptr.String("TestTag"), + Value: ptr.String("test-tgw"), + }, + }, + } + + assert.Equal(t, "tgw-1234567890abcdef0", tgw.Properties().Get("ID")) + assert.Equal(t, "123456789012", tgw.Properties().Get("OwnerId")) + assert.Equal(t, "test-tgw", tgw.Properties().Get("tag:TestTag")) + assert.Equal(t, "tgw-1234567890abcdef0", tgw.String()) +} diff --git a/resources/gamelift-build.go b/resources/gamelift-build.go index 57808692..2e69b3ba 100644 --- a/resources/gamelift-build.go +++ b/resources/gamelift-build.go @@ -24,12 +24,22 @@ func init() { }) } -type GameLiftBuildLister struct{} +type GameLiftBuildLister struct { + GameLift +} func (l *GameLiftBuildLister) List(_ context.Context, o interface{}) ([]resource.Resource, error) { opts := o.(*nuke.ListerOpts) var resources []resource.Resource + if !l.IsSupportedRegion(opts.Region.Name) { + opts.Logger. + WithField("resource", GameLiftBuildResource). + WithField("region", opts.Region.Name). + Debug("region not supported") + return resources, nil + } + svc := gamelift.New(opts.Session) params := &gamelift.ListBuildsInput{} diff --git a/resources/gamelift-fleet.go b/resources/gamelift-fleet.go index b4c52f18..90b3507f 100644 --- a/resources/gamelift-fleet.go +++ b/resources/gamelift-fleet.go @@ -23,12 +23,22 @@ func init() { }) } -type GameLiftFleetLister struct{} +type GameLiftFleetLister struct { + GameLift +} func (l *GameLiftFleetLister) List(_ context.Context, o interface{}) ([]resource.Resource, error) { opts := o.(*nuke.ListerOpts) var resources []resource.Resource + if !l.IsSupportedRegion(opts.Region.Name) { + opts.Logger. + WithField("resource", GameLiftFleetResource). + WithField("region", opts.Region.Name). + Debug("region not supported") + return resources, nil + } + svc := gamelift.New(opts.Session) params := &gamelift.ListFleetsInput{} diff --git a/resources/gamelift-mm-config.go b/resources/gamelift-mm-config.go index 797f2fcf..9ed9b66c 100644 --- a/resources/gamelift-mm-config.go +++ b/resources/gamelift-mm-config.go @@ -25,12 +25,22 @@ func init() { }) } -type GameLiftMatchmakingConfigurationLister struct{} +type GameLiftMatchmakingConfigurationLister struct { + GameLift +} func (l *GameLiftMatchmakingConfigurationLister) List(_ context.Context, o interface{}) ([]resource.Resource, error) { opts := o.(*nuke.ListerOpts) var resources []resource.Resource + if !l.IsSupportedRegion(opts.Region.Name) { + opts.Logger. + WithField("resource", GameLiftMatchmakingConfigurationResource). + WithField("region", opts.Region.Name). + Debug("region not supported") + return resources, nil + } + svc := gamelift.New(opts.Session) params := &gamelift.DescribeMatchmakingConfigurationsInput{} diff --git a/resources/gamelift-mm-rule.go b/resources/gamelift-mm-rule.go index 81fabee0..e2cf5787 100644 --- a/resources/gamelift-mm-rule.go +++ b/resources/gamelift-mm-rule.go @@ -24,12 +24,22 @@ func init() { }) } -type GameLiftMatchmakingRuleSetLister struct{} +type GameLiftMatchmakingRuleSetLister struct { + GameLift +} func (l *GameLiftMatchmakingRuleSetLister) List(_ context.Context, o interface{}) ([]resource.Resource, error) { opts := o.(*nuke.ListerOpts) var resources []resource.Resource + if !l.IsSupportedRegion(opts.Region.Name) { + opts.Logger. + WithField("resource", GameLiftMatchmakingRuleSetResource). + WithField("region", opts.Region.Name). + Debug("region not supported") + return resources, nil + } + svc := gamelift.New(opts.Session) params := &gamelift.DescribeMatchmakingRuleSetsInput{} diff --git a/resources/gamelift-queue.go b/resources/gamelift-queue.go index 00f39188..74f82bb7 100644 --- a/resources/gamelift-queue.go +++ b/resources/gamelift-queue.go @@ -23,12 +23,22 @@ func init() { }) } -type GameLiftQueueLister struct{} +type GameLiftQueueLister struct { + GameLift +} func (l *GameLiftQueueLister) List(_ context.Context, o interface{}) ([]resource.Resource, error) { opts := o.(*nuke.ListerOpts) var resources []resource.Resource + if !l.IsSupportedRegion(opts.Region.Name) { + opts.Logger. + WithField("resource", GameLiftQueueResource). + WithField("region", opts.Region.Name). + Debug("region not supported") + return resources, nil + } + svc := gamelift.New(opts.Session) params := &gamelift.DescribeGameSessionQueuesInput{} diff --git a/resources/gamelift.go b/resources/gamelift.go new file mode 100644 index 00000000..8503c331 --- /dev/null +++ b/resources/gamelift.go @@ -0,0 +1,23 @@ +package resources + +import "slices" + +type GameLift struct{} + +func (g *GameLift) IsSupportedRegion(region string) bool { + // ref: https://docs.aws.amazon.com/gamelift/latest/developerguide/gamelift-regions.html + // there are fewer unsupported, so doing the inverse + // unsupported are regions that only support "Remote location for multi-location fleets" + // note: we do not currently filter down to the local zone + unsupportedRegions := []string{ + "af-south-1", + "ap-east-1", + "ap-northeast-3", + "eu-north-1", + "eu-south-1", + "eu-west-3", + "me-south-1", + } + + return !slices.Contains(unsupportedRegions, region) +} diff --git a/resources/iam-account-setting-password-policy.go b/resources/iam-account-setting-password-policy.go index 0e555db8..46af99ab 100644 --- a/resources/iam-account-setting-password-policy.go +++ b/resources/iam-account-setting-password-policy.go @@ -2,7 +2,6 @@ package resources import ( "context" - "errors" "github.com/aws/aws-sdk-go/aws/awserr" @@ -11,6 +10,7 @@ import ( "github.com/ekristen/libnuke/pkg/registry" "github.com/ekristen/libnuke/pkg/resource" + "github.com/ekristen/libnuke/pkg/types" "github.com/ekristen/aws-nuke/v3/pkg/nuke" ) @@ -64,8 +64,8 @@ type IAMAccountSettingPasswordPolicy struct { policy *iam.PasswordPolicy } -func (e *IAMAccountSettingPasswordPolicy) Remove(_ context.Context) error { - _, err := e.svc.DeleteAccountPasswordPolicy(&iam.DeleteAccountPasswordPolicyInput{}) +func (r *IAMAccountSettingPasswordPolicy) Remove(_ context.Context) error { + _, err := r.svc.DeleteAccountPasswordPolicy(&iam.DeleteAccountPasswordPolicyInput{}) if err != nil { return err } @@ -73,6 +73,10 @@ func (e *IAMAccountSettingPasswordPolicy) Remove(_ context.Context) error { return nil } -func (e *IAMAccountSettingPasswordPolicy) String() string { +func (r *IAMAccountSettingPasswordPolicy) String() string { return "custom" } + +func (r *IAMAccountSettingPasswordPolicy) Properties() types.Properties { + return types.NewProperties().Set("type", "custom") +} diff --git a/resources/iam-role.go b/resources/iam-role.go index a90a0243..ccc3e8f4 100644 --- a/resources/iam-role.go +++ b/resources/iam-role.go @@ -30,6 +30,7 @@ func init() { Lister: &IAMRoleLister{}, DependsOn: []string{ IAMRolePolicyAttachmentResource, + CloudFormationStackResource, // IAM roles can be used in deletion of CloudFormation stacks }, DeprecatedAliases: []string{ "IamRole", diff --git a/resources/kms-alias.go b/resources/kms-alias.go index f8669f18..aa13b603 100644 --- a/resources/kms-alias.go +++ b/resources/kms-alias.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "strings" + "time" "github.com/aws/aws-sdk-go/service/kms" "github.com/aws/aws-sdk-go/service/kms/kmsiface" @@ -43,9 +44,21 @@ func (l *KMSAliasLister) List(_ context.Context, o interface{}) ([]resource.Reso err := svc.ListAliasesPages(nil, func(page *kms.ListAliasesOutput, lastPage bool) bool { for _, alias := range page.Aliases { + var tags []*kms.Tag + if alias.TargetKeyId != nil { + keyTags, err := svc.ListResourceTags(&kms.ListResourceTagsInput{ + KeyId: alias.TargetKeyId, + }) + if err != nil { + opts.Logger.WithError(err).Debug("failed to list tags for key for the alias") + } + tags = keyTags.Tags + } + resources = append(resources, &KMSAlias{ svc: svc, Name: alias.AliasName, + Tags: tags, }) } return true @@ -58,8 +71,11 @@ func (l *KMSAliasLister) List(_ context.Context, o interface{}) ([]resource.Reso } type KMSAlias struct { - svc kmsiface.KMSAPI - Name *string + svc kmsiface.KMSAPI + Name *string `description:"The name of the KMS alias"` + CreationDate *time.Time `description:"The creation date of the KMS alias"` + TargetKeyID *string `description:"The KMS Key ID that the alias points to"` + Tags []*kms.Tag `property:"tagPrefix=key:tag"` } func (r *KMSAlias) Filter() error { diff --git a/resources/kms-key.go b/resources/kms-key.go index 04ce5f13..ce600c24 100644 --- a/resources/kms-key.go +++ b/resources/kms-key.go @@ -15,6 +15,7 @@ import ( "github.com/ekristen/libnuke/pkg/registry" "github.com/ekristen/libnuke/pkg/resource" + libsettings "github.com/ekristen/libnuke/pkg/settings" "github.com/ekristen/libnuke/pkg/types" "github.com/ekristen/aws-nuke/v3/pkg/nuke" @@ -31,6 +32,9 @@ func init() { DependsOn: []string{ KMSAliasResource, }, + Settings: []string{ + "IgnoreErrors", + }, }) } @@ -88,7 +92,7 @@ func (l *KMSKeyLister) List(_ context.Context, o interface{}) ([]resource.Resour if errors.As(err, &awsError) { if awsError.Code() == "AccessDeniedException" { inaccessibleKeys = true - logrus.WithError(err).Debug("unable to list tags") + logrus.WithError(err).Debug("unable to list tags - inaccessible key") continue } else { logrus.WithError(err).Error("unable to list tags") @@ -126,12 +130,13 @@ func (l *KMSKeyLister) List(_ context.Context, o interface{}) ([]resource.Resour } type KMSKey struct { - svc kmsiface.KMSAPI - ID *string - State *string - Manager *string - Alias *string - Tags []*kms.Tag + svc kmsiface.KMSAPI + settings *libsettings.Setting + ID *string + State *string + Manager *string + Alias *string + Tags []*kms.Tag } func (r *KMSKey) Filter() error { @@ -151,6 +156,12 @@ func (r *KMSKey) Remove(_ context.Context) error { KeyId: r.ID, PendingWindowInDays: aws.Int64(7), }) + + // Ignore errors if the setting is enabled as AWS KMS keys can be in a state where they can't be deleted and we don't want to fail the whole nuke if setting is enabled + if err != nil && r.settings.GetBool("IgnoreErrors") { + fmt.Printf("ignoring error for key %s. Error: %v\n", *r.ID, err) + return nil + } return err } @@ -158,6 +169,10 @@ func (r *KMSKey) String() string { return *r.ID } +func (r *KMSKey) Settings(settings *libsettings.Setting) { + r.settings = settings +} + func (r *KMSKey) Properties() types.Properties { return types.NewPropertiesFromStruct(r) } diff --git a/resources/mobile-projects.go b/resources/mobile-projects.go deleted file mode 100644 index 48b5a763..00000000 --- a/resources/mobile-projects.go +++ /dev/null @@ -1,77 +0,0 @@ -package resources - -import ( - "context" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/mobile" - - "github.com/ekristen/libnuke/pkg/registry" - "github.com/ekristen/libnuke/pkg/resource" - - "github.com/ekristen/aws-nuke/v3/pkg/nuke" -) - -const MobileProjectResource = "MobileProject" - -func init() { - registry.Register(®istry.Registration{ - Name: MobileProjectResource, - Scope: nuke.Account, - Resource: &MobileProject{}, - Lister: &MobileProjectLister{}, - }) -} - -type MobileProjectLister struct{} - -func (l *MobileProjectLister) List(_ context.Context, o interface{}) ([]resource.Resource, error) { - opts := o.(*nuke.ListerOpts) - - svc := mobile.New(opts.Session) - svc.ClientInfo.SigningName = "AWSMobileHubService" - resources := make([]resource.Resource, 0) - - params := &mobile.ListProjectsInput{ - MaxResults: aws.Int64(100), - } - - for { - output, err := svc.ListProjects(params) - if err != nil { - return nil, err - } - - for _, project := range output.Projects { - resources = append(resources, &MobileProject{ - svc: svc, - projectID: project.ProjectId, - }) - } - - if output.NextToken == nil { - break - } - - params.NextToken = output.NextToken - } - - return resources, nil -} - -type MobileProject struct { - svc *mobile.Mobile - projectID *string -} - -func (f *MobileProject) Remove(_ context.Context) error { - _, err := f.svc.DeleteProject(&mobile.DeleteProjectInput{ - ProjectId: f.projectID, - }) - - return err -} - -func (f *MobileProject) String() string { - return *f.projectID -} diff --git a/resources/msk-cluster.go b/resources/msk-cluster.go index 31b221ba..402d18ee 100644 --- a/resources/msk-cluster.go +++ b/resources/msk-cluster.go @@ -3,8 +3,8 @@ package resources import ( "context" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/kafka" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/kafka" "github.com/ekristen/libnuke/pkg/registry" "github.com/ekristen/libnuke/pkg/resource" @@ -26,42 +26,45 @@ func init() { type MSKClusterLister struct{} -func (l *MSKClusterLister) List(_ context.Context, o interface{}) ([]resource.Resource, error) { +func (l *MSKClusterLister) List(ctx context.Context, o interface{}) ([]resource.Resource, error) { opts := o.(*nuke.ListerOpts) + svc := kafka.NewFromConfig(*opts.Config) - svc := kafka.New(opts.Session) + params := &kafka.ListClustersV2Input{} + resp, err := svc.ListClustersV2(ctx, params) - resp, err := svc.ListClusters(&kafka.ListClustersInput{}) if err != nil { return nil, err } - resources := make([]resource.Resource, 0) for _, cluster := range resp.ClusterInfoList { resources = append(resources, &MSKCluster{ - svc: svc, - arn: *cluster.ClusterArn, - name: *cluster.ClusterName, - tags: cluster.Tags, + svc: svc, + context: ctx, + arn: *cluster.ClusterArn, + name: *cluster.ClusterName, + tags: cluster.Tags, }) - } + } return resources, nil } type MSKCluster struct { - svc *kafka.Kafka + svc *kafka.Client + context context.Context + arn string name string - tags map[string]*string + tags map[string]string } -func (m *MSKCluster) Remove(_ context.Context) error { +func (m *MSKCluster) Remove(ctx context.Context) error { params := &kafka.DeleteClusterInput{ ClusterArn: &m.arn, } - _, err := m.svc.DeleteCluster(params) + _, err := m.svc.DeleteCluster(ctx, params) if err != nil { return err } diff --git a/resources/neptune-cluster.go b/resources/neptune-cluster.go new file mode 100644 index 00000000..9d20535d --- /dev/null +++ b/resources/neptune-cluster.go @@ -0,0 +1,135 @@ +package resources + +import ( + "context" + "fmt" + + "github.com/gotidy/ptr" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/neptune" + + "github.com/ekristen/libnuke/pkg/registry" + "github.com/ekristen/libnuke/pkg/resource" + libsettings "github.com/ekristen/libnuke/pkg/settings" + "github.com/ekristen/libnuke/pkg/types" + + "github.com/ekristen/aws-nuke/v3/pkg/nuke" +) + +const NeptuneClusterResource = "NeptuneCluster" + +func init() { + registry.Register(®istry.Registration{ + Name: NeptuneClusterResource, + Scope: nuke.Account, + Resource: &NeptuneCluster{}, + Lister: &NeptuneClusterLister{}, + DependsOn: []string{ + NeptuneInstanceResource, + }, + Settings: []string{ + "DisableDeletionProtection", + }, + }) +} + +type NeptuneClusterLister struct{} + +func (l *NeptuneClusterLister) List(_ context.Context, o interface{}) ([]resource.Resource, error) { + opts := o.(*nuke.ListerOpts) + + svc := neptune.New(opts.Session) + resources := make([]resource.Resource, 0) + + params := &neptune.DescribeDBClustersInput{ + Filters: []*neptune.Filter{ + { + Name: aws.String("engine"), + Values: []*string{aws.String("neptune")}, + }, + }, + MaxRecords: aws.Int64(100), + } + + for { + output, err := svc.DescribeDBClusters(params) + if err != nil { + return nil, err + } + + for _, dbCluster := range output.DBClusters { + var dbTags []*neptune.Tag + tags, err := svc.ListTagsForResource(&neptune.ListTagsForResourceInput{ + ResourceName: dbCluster.DBClusterArn, + }) + if err != nil { + opts.Logger.WithError(err).Warn("failed to list tags for resource") + } else { + dbTags = tags.TagList + } + + resources = append(resources, &NeptuneCluster{ + svc: svc, + ID: dbCluster.DBClusterIdentifier, + Status: dbCluster.Status, + Tags: dbTags, + }) + } + + if output.Marker == nil { + break + } + + params.Marker = output.Marker + } + + return resources, nil +} + +type NeptuneCluster struct { + svc *neptune.Neptune + settings *libsettings.Setting + + ID *string + Status *string + Tags []*neptune.Tag +} + +func (r *NeptuneCluster) Settings(settings *libsettings.Setting) { + r.settings = settings +} + +func (r *NeptuneCluster) Filter() error { + if ptr.ToString(r.Status) == "deleting" { + return fmt.Errorf("already deleting") + } + return nil +} + +func (r *NeptuneCluster) Remove(_ context.Context) error { + if r.settings.GetBool("DisableDeletionProtection") { + _, err := r.svc.ModifyDBCluster(&neptune.ModifyDBClusterInput{ + DBClusterIdentifier: r.ID, + DeletionProtection: ptr.Bool(false), + }) + if err != nil { + return err + } + } + + _, err := r.svc.DeleteDBCluster(&neptune.DeleteDBClusterInput{ + DBClusterIdentifier: r.ID, + SkipFinalSnapshot: ptr.Bool(true), + }) + + return err +} + +func (r *NeptuneCluster) Properties() types.Properties { + return types.NewPropertiesFromStruct(r) +} + +func (r *NeptuneCluster) String() string { + return *r.ID +} diff --git a/resources/neptune-clusters.go b/resources/neptune-clusters.go deleted file mode 100644 index 7e1c6781..00000000 --- a/resources/neptune-clusters.go +++ /dev/null @@ -1,77 +0,0 @@ -package resources - -import ( - "context" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/neptune" - - "github.com/ekristen/libnuke/pkg/registry" - "github.com/ekristen/libnuke/pkg/resource" - - "github.com/ekristen/aws-nuke/v3/pkg/nuke" -) - -const NeptuneClusterResource = "NeptuneCluster" - -func init() { - registry.Register(®istry.Registration{ - Name: NeptuneClusterResource, - Scope: nuke.Account, - Resource: &NeptuneCluster{}, - Lister: &NeptuneClusterLister{}, - }) -} - -type NeptuneClusterLister struct{} - -func (l *NeptuneClusterLister) List(_ context.Context, o interface{}) ([]resource.Resource, error) { - opts := o.(*nuke.ListerOpts) - - svc := neptune.New(opts.Session) - resources := make([]resource.Resource, 0) - - params := &neptune.DescribeDBClustersInput{ - MaxRecords: aws.Int64(100), - } - - for { - output, err := svc.DescribeDBClusters(params) - if err != nil { - return nil, err - } - - for _, dbCluster := range output.DBClusters { - resources = append(resources, &NeptuneCluster{ - svc: svc, - ID: dbCluster.DBClusterIdentifier, - }) - } - - if output.Marker == nil { - break - } - - params.Marker = output.Marker - } - - return resources, nil -} - -type NeptuneCluster struct { - svc *neptune.Neptune - ID *string -} - -func (f *NeptuneCluster) Remove(_ context.Context) error { - _, err := f.svc.DeleteDBCluster(&neptune.DeleteDBClusterInput{ - DBClusterIdentifier: f.ID, - SkipFinalSnapshot: aws.Bool(true), - }) - - return err -} - -func (f *NeptuneCluster) String() string { - return *f.ID -} diff --git a/resources/neptune-instance.go b/resources/neptune-instance.go index 0d846b60..1e24100c 100644 --- a/resources/neptune-instance.go +++ b/resources/neptune-instance.go @@ -2,7 +2,9 @@ package resources import ( "context" + "fmt" + "github.com/gotidy/ptr" "github.com/sirupsen/logrus" "github.com/aws/aws-sdk-go/aws" @@ -10,6 +12,7 @@ import ( "github.com/ekristen/libnuke/pkg/registry" "github.com/ekristen/libnuke/pkg/resource" + libsettings "github.com/ekristen/libnuke/pkg/settings" "github.com/ekristen/libnuke/pkg/types" "github.com/ekristen/aws-nuke/v3/pkg/nuke" @@ -23,6 +26,10 @@ func init() { Scope: nuke.Account, Resource: &NeptuneInstance{}, Lister: &NeptuneInstanceLister{}, + Settings: []string{ + "DisableClusterDeletionProtection", + "DisableDeletionProtection", + }, }) } @@ -63,10 +70,12 @@ func (l *NeptuneInstanceLister) List(_ context.Context, o interface{}) ([]resour } resources = append(resources, &NeptuneInstance{ - svc: svc, - ID: dbInstance.DBInstanceIdentifier, - Name: dbInstance.DBName, - Tags: dbTags, + svc: svc, + ID: dbInstance.DBInstanceIdentifier, + ClusterID: dbInstance.DBClusterIdentifier, + Name: dbInstance.DBName, + Status: dbInstance.DBInstanceStatus, + Tags: dbTags, }) } @@ -81,16 +90,50 @@ func (l *NeptuneInstanceLister) List(_ context.Context, o interface{}) ([]resour } type NeptuneInstance struct { - svc *neptune.Neptune - ID *string - Name *string - Tags []*neptune.Tag + svc *neptune.Neptune + settings *libsettings.Setting + ID *string + ClusterID *string + Name *string + Status *string + Tags []*neptune.Tag +} + +func (r *NeptuneInstance) Settings(settings *libsettings.Setting) { + r.settings = settings +} + +func (r *NeptuneInstance) Filter() error { + if ptr.ToString(r.Status) == "deleting" { + return fmt.Errorf("already deleting") + } + return nil } func (r *NeptuneInstance) Remove(_ context.Context) error { + if r.settings.GetBool("DisableClusterDeletionProtection") { + _, err := r.svc.ModifyDBCluster(&neptune.ModifyDBClusterInput{ + DBClusterIdentifier: r.ClusterID, + DeletionProtection: ptr.Bool(false), + }) + if err != nil { + return err + } + } + + if r.settings.GetBool("DisableDeletionProtection") { + _, err := r.svc.ModifyDBInstance(&neptune.ModifyDBInstanceInput{ + DBInstanceIdentifier: r.ID, + DeletionProtection: ptr.Bool(false), + }) + if err != nil { + return err + } + } + _, err := r.svc.DeleteDBInstance(&neptune.DeleteDBInstanceInput{ DBInstanceIdentifier: r.ID, - SkipFinalSnapshot: aws.Bool(true), + SkipFinalSnapshot: ptr.Bool(true), }) return err diff --git a/resources/neptune-snapshots.go b/resources/neptune-snapshot.go similarity index 62% rename from resources/neptune-snapshots.go rename to resources/neptune-snapshot.go index bfbde6cb..f1979dd0 100644 --- a/resources/neptune-snapshots.go +++ b/resources/neptune-snapshot.go @@ -2,12 +2,14 @@ package resources import ( "context" + "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/neptune" "github.com/ekristen/libnuke/pkg/registry" "github.com/ekristen/libnuke/pkg/resource" + "github.com/ekristen/libnuke/pkg/types" "github.com/ekristen/aws-nuke/v3/pkg/nuke" ) @@ -36,6 +38,12 @@ func (l *NeptuneSnapshotLister) List(_ context.Context, o interface{}) ([]resour params := &neptune.DescribeDBClusterSnapshotsInput{ MaxRecords: aws.Int64(100), + Filters: []*neptune.Filter{ + { + Name: aws.String("engine"), + Values: []*string{aws.String("neptune")}, + }, + }, } for { @@ -46,8 +54,10 @@ func (l *NeptuneSnapshotLister) List(_ context.Context, o interface{}) ([]resour for _, dbClusterSnapshot := range output.DBClusterSnapshots { resources = append(resources, &NeptuneSnapshot{ - svc: svc, - ID: dbClusterSnapshot.DBClusterSnapshotIdentifier, + svc: svc, + ID: dbClusterSnapshot.DBClusterSnapshotIdentifier, + Status: dbClusterSnapshot.Status, + CreateTime: dbClusterSnapshot.SnapshotCreateTime, }) } @@ -62,18 +72,24 @@ func (l *NeptuneSnapshotLister) List(_ context.Context, o interface{}) ([]resour } type NeptuneSnapshot struct { - svc *neptune.Neptune - ID *string + svc *neptune.Neptune + ID *string + Status *string + CreateTime *time.Time } -func (f *NeptuneSnapshot) Remove(_ context.Context) error { - _, err := f.svc.DeleteDBClusterSnapshot(&neptune.DeleteDBClusterSnapshotInput{ - DBClusterSnapshotIdentifier: f.ID, +func (r *NeptuneSnapshot) Remove(_ context.Context) error { + _, err := r.svc.DeleteDBClusterSnapshot(&neptune.DeleteDBClusterSnapshotInput{ + DBClusterSnapshotIdentifier: r.ID, }) return err } -func (f *NeptuneSnapshot) String() string { - return *f.ID +func (r *NeptuneSnapshot) String() string { + return *r.ID +} + +func (r *NeptuneSnapshot) Properties() types.Properties { + return types.NewPropertiesFromStruct(r) } diff --git a/resources/network-firewall.go b/resources/network-firewall.go new file mode 100644 index 00000000..3137fddb --- /dev/null +++ b/resources/network-firewall.go @@ -0,0 +1,129 @@ +package resources + +import ( + "context" + + "github.com/ekristen/aws-nuke/v3/pkg/nuke" + "github.com/ekristen/libnuke/pkg/registry" + "github.com/ekristen/libnuke/pkg/resource" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/networkfirewall" + networkfirwallTypes "github.com/aws/aws-sdk-go-v2/service/networkfirewall/types" + "github.com/rebuy-de/aws-nuke/v2/pkg/types" +) + +type NetworkFirewall struct { + svc *networkfirewall.Client + + firewall networkfirwallTypes.FirewallMetadata + logConfig *networkfirwallTypes.LoggingConfiguration + tags []networkfirwallTypes.Tag +} + +const NetworkFirewallResource = "NetworkFirewall" + +func init() { + registry.Register(®istry.Registration{ + Name: NetworkFirewallResource, + Scope: nuke.Account, + Resource: &NetworkFirewall{}, + Lister: &NetworkFirewallLister{}, + AlternativeResource: "AWS::NetworkFirewall::Firewall", + }) +} + +type NetworkFirewallLister struct{} + +func (l *NetworkFirewallLister) List(ctx context.Context, o interface{}) ([]resource.Resource, error) { + opts := o.(*nuke.ListerOpts) + svc := networkfirewall.NewFromConfig(*opts.Config) + + resources := make([]resource.Resource, 0) + params := &networkfirewall.ListFirewallsInput{ + MaxResults: aws.Int32(100), + } + + for { + resp, err := svc.ListFirewalls(ctx, params) + if err != nil { + return nil, err + } + for _, firewall := range resp.Firewalls { + tagParams := &networkfirewall.ListTagsForResourceInput{ + ResourceArn: firewall.FirewallArn, + MaxResults: aws.Int32(100), + } + tags := []networkfirwallTypes.Tag{} + for { + tagResp, tagErr := svc.ListTagsForResource(ctx, tagParams) + if tagErr != nil { + return nil, tagErr + } + tags = append(tags, tagResp.Tags...) + if tagResp.NextToken == nil { + break + } + tagParams.NextToken = tagResp.NextToken + } + // logging configuration required to delete firewall + logResp, err := svc.DescribeLoggingConfiguration(ctx, &networkfirewall.DescribeLoggingConfigurationInput{ + FirewallArn: firewall.FirewallArn, + }) + if err != nil { + return nil, err + } + resources = append(resources, &NetworkFirewall{ + svc: svc, + firewall: firewall, + logConfig: logResp.LoggingConfiguration, + tags: tags, + }) + } + if resp.NextToken == nil { + break + } + params.NextToken = resp.NextToken + } + return resources, nil +} + +func (i *NetworkFirewall) Remove(ctx context.Context) error { + if i.logConfig != nil { + for index := 1; index <= len(i.logConfig.LogDestinationConfigs); index++ { + // aws forces to only remove one at a time + _, err := i.svc.UpdateLoggingConfiguration(ctx, &networkfirewall.UpdateLoggingConfigurationInput{ + FirewallArn: i.firewall.FirewallArn, + LoggingConfiguration: &networkfirwallTypes.LoggingConfiguration{ + LogDestinationConfigs: i.logConfig.LogDestinationConfigs[index:], + }, + }) + if err != nil { + return err + } + } + } + params := &networkfirewall.DeleteFirewallInput{ + FirewallArn: i.firewall.FirewallArn, + } + _, err := i.svc.DeleteFirewall(ctx, params) + if err != nil { + return err + } + return nil +} + +func (i *NetworkFirewall) Properties() types.Properties { + properties := types.NewProperties() + properties.Set("ARN", *i.firewall.FirewallArn) + properties.Set("Name", *i.firewall.FirewallName) + properties.Set("Logging Configured", i.logConfig != nil) + for _, tag := range i.tags { + properties.SetTag(tag.Key, *tag.Value) + } + return properties +} + +func (i *NetworkFirewall) String() string { + return *i.firewall.FirewallArn +} diff --git a/resources/rds-instances.go b/resources/rds-instances.go index 7bc0af26..997fd93d 100644 --- a/resources/rds-instances.go +++ b/resources/rds-instances.go @@ -4,6 +4,8 @@ import ( "context" "time" + "github.com/gotidy/ptr" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/rds" @@ -52,6 +54,12 @@ func (l *RDSInstanceLister) List(_ context.Context, o interface{}) ([]resource.R resources := make([]resource.Resource, 0) for _, instance := range resp.DBInstances { + // Note: NeptuneInstance handles Neptune instances + if ptr.ToString(instance.Engine) == "neptune" { + opts.Logger.Debug("skipping neptune instance, it is handled by NeptuneInstance") + continue + } + tags, err := svc.ListTagsForResource(&rds.ListTagsForResourceInput{ ResourceName: instance.DBInstanceArn, }) diff --git a/resources/resource-explorer2-indexes.go b/resources/resource-explorer2-index.go similarity index 65% rename from resources/resource-explorer2-indexes.go rename to resources/resource-explorer2-index.go index d771d6d4..06a5ea4c 100644 --- a/resources/resource-explorer2-indexes.go +++ b/resources/resource-explorer2-index.go @@ -5,9 +5,12 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/resourceexplorer2" - "github.com/ekristen/aws-nuke/v3/pkg/nuke" + "github.com/ekristen/libnuke/pkg/registry" "github.com/ekristen/libnuke/pkg/resource" + "github.com/ekristen/libnuke/pkg/types" + + "github.com/ekristen/aws-nuke/v3/pkg/nuke" ) const ResourceExplorer2IndexResource = "ResourceExplorer2Index" @@ -23,17 +26,15 @@ func init() { type ResourceExplorer2IndexLister struct{} -type ResourceExplorer2Index struct { - svc *resourceexplorer2.ResourceExplorer2 - indexArn *string -} - func (l *ResourceExplorer2IndexLister) List(_ context.Context, o interface{}) ([]resource.Resource, error) { opts := o.(*nuke.ListerOpts) svc := resourceexplorer2.New(opts.Session) var resources []resource.Resource - params := &resourceexplorer2.ListIndexesInput{} + params := &resourceexplorer2.ListIndexesInput{ + Regions: aws.StringSlice([]string{opts.Region.Name}), + MaxResults: aws.Int64(100), + } for { output, err := svc.ListIndexes(params) @@ -43,8 +44,9 @@ func (l *ResourceExplorer2IndexLister) List(_ context.Context, o interface{}) ([ for _, index := range output.Indexes { resources = append(resources, &ResourceExplorer2Index{ - svc: svc, - indexArn: index.Arn, + svc: svc, + ARN: index.Arn, + Type: index.Type, }) } @@ -58,14 +60,24 @@ func (l *ResourceExplorer2IndexLister) List(_ context.Context, o interface{}) ([ return resources, nil } -func (f *ResourceExplorer2Index) Remove(_ context.Context) error { - _, err := f.svc.DeleteIndex(&resourceexplorer2.DeleteIndexInput{ - Arn: f.indexArn, +type ResourceExplorer2Index struct { + svc *resourceexplorer2.ResourceExplorer2 + ARN *string + Type *string +} + +func (r *ResourceExplorer2Index) Remove(_ context.Context) error { + _, err := r.svc.DeleteIndex(&resourceexplorer2.DeleteIndexInput{ + Arn: r.ARN, }) return err } -func (f *ResourceExplorer2Index) String() string { - return *f.indexArn +func (r *ResourceExplorer2Index) String() string { + return *r.ARN +} + +func (r *ResourceExplorer2Index) Properties() types.Properties { + return types.NewPropertiesFromStruct(r) } diff --git a/resources/resource-explorer2-views.go b/resources/resource-explorer2-view.go similarity index 71% rename from resources/resource-explorer2-views.go rename to resources/resource-explorer2-view.go index 9f11a32f..99540efd 100644 --- a/resources/resource-explorer2-views.go +++ b/resources/resource-explorer2-view.go @@ -5,9 +5,12 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/resourceexplorer2" - "github.com/ekristen/aws-nuke/v3/pkg/nuke" + "github.com/ekristen/libnuke/pkg/registry" "github.com/ekristen/libnuke/pkg/resource" + "github.com/ekristen/libnuke/pkg/types" + + "github.com/ekristen/aws-nuke/v3/pkg/nuke" ) const ResourceExplorer2ViewResource = "ResourceExplorer2View" @@ -23,11 +26,6 @@ func init() { type ResourceExplorer2ViewLister struct{} -type ResourceExplorer2View struct { - svc *resourceexplorer2.ResourceExplorer2 - viewArn *string -} - func (l *ResourceExplorer2ViewLister) List(_ context.Context, o interface{}) ([]resource.Resource, error) { opts := o.(*nuke.ListerOpts) svc := resourceexplorer2.New(opts.Session) @@ -43,8 +41,8 @@ func (l *ResourceExplorer2ViewLister) List(_ context.Context, o interface{}) ([] for _, view := range output.Views { resources = append(resources, &ResourceExplorer2View{ - svc: svc, - viewArn: view, + svc: svc, + ARN: view, }) } @@ -58,14 +56,23 @@ func (l *ResourceExplorer2ViewLister) List(_ context.Context, o interface{}) ([] return resources, nil } -func (f *ResourceExplorer2View) Remove(_ context.Context) error { - _, err := f.svc.DeleteView(&resourceexplorer2.DeleteViewInput{ - ViewArn: f.viewArn, +type ResourceExplorer2View struct { + svc *resourceexplorer2.ResourceExplorer2 + ARN *string `description:"The ARN of the Resource Explorer View"` +} + +func (r *ResourceExplorer2View) Remove(_ context.Context) error { + _, err := r.svc.DeleteView(&resourceexplorer2.DeleteViewInput{ + ViewArn: r.ARN, }) return err } -func (f *ResourceExplorer2View) String() string { - return *f.viewArn +func (r *ResourceExplorer2View) String() string { + return *r.ARN +} + +func (r *ResourceExplorer2View) Properties() types.Properties { + return types.NewPropertiesFromStruct(r) } diff --git a/resources/route53-resource-records.go b/resources/route53-resource-record.go similarity index 72% rename from resources/route53-resource-records.go rename to resources/route53-resource-record.go index 70bab19d..610e5f89 100644 --- a/resources/route53-resource-records.go +++ b/resources/route53-resource-record.go @@ -44,7 +44,7 @@ func (l *Route53ResourceRecordSetLister) List(ctx context.Context, o interface{} for _, r := range sub { zone := r.(*Route53HostedZone) - rrs, err := ListResourceRecordsForZone(svc, zone.id, zone.name) + rrs, err := ListResourceRecordsForZone(svc, zone.id, zone.name, zone.tags) if err != nil { return nil, err } @@ -55,13 +55,13 @@ func (l *Route53ResourceRecordSetLister) List(ctx context.Context, o interface{} return resources, nil } -func ListResourceRecordsForZone(svc *route53.Route53, zoneID, zoneName *string) ([]resource.Resource, error) { +func ListResourceRecordsForZone(svc *route53.Route53, zoneID, zoneName *string, zoneTags []*route53.Tag) ([]resource.Resource, error) { + resources := make([]resource.Resource, 0) + params := &route53.ListResourceRecordSetsInput{ HostedZoneId: zoneID, } - resources := make([]resource.Resource, 0) - for { resp, err := svc.ListResourceRecordSets(params) if err != nil { @@ -70,10 +70,13 @@ func ListResourceRecordsForZone(svc *route53.Route53, zoneID, zoneName *string) for _, rrs := range resp.ResourceRecordSets { resources = append(resources, &Route53ResourceRecordSet{ - svc: svc, - hostedZoneID: zoneID, - hostedZoneName: zoneName, - data: rrs, + svc: svc, + hostedZoneID: zoneID, + HostedZoneName: zoneName, + resourceRecordSet: rrs, + Name: rrs.Name, + Type: rrs.Type, + Tags: zoneTags, }) } @@ -90,19 +93,22 @@ func ListResourceRecordsForZone(svc *route53.Route53, zoneID, zoneName *string) } type Route53ResourceRecordSet struct { - svc *route53.Route53 - hostedZoneID *string - hostedZoneName *string - data *route53.ResourceRecordSet - changeID *string + svc *route53.Route53 + resourceRecordSet *route53.ResourceRecordSet // Note: this is required for the deletion + changeID *string + hostedZoneID *string + HostedZoneName *string `description:"The name of the zone to which the record belongs"` + Name *string `description:"The name of the record"` + Type *string `description:"The type of the record"` + Tags []*route53.Tag `property:"tagPrefix=zone:tag"` } func (r *Route53ResourceRecordSet) Filter() error { - if *r.data.Type == "NS" && *r.hostedZoneName == *r.data.Name { + if *r.Type == "NS" && *r.HostedZoneName == *r.Name { return fmt.Errorf("cannot delete NS record") } - if *r.data.Type == "SOA" { + if *r.Type == "SOA" { return fmt.Errorf("cannot delete SOA record") } @@ -116,7 +122,7 @@ func (r *Route53ResourceRecordSet) Remove(_ context.Context) error { Changes: []*route53.Change{ { Action: aws.String("DELETE"), - ResourceRecordSet: r.data, + ResourceRecordSet: r.resourceRecordSet, }, }, }, @@ -133,11 +139,9 @@ func (r *Route53ResourceRecordSet) Remove(_ context.Context) error { } func (r *Route53ResourceRecordSet) Properties() types.Properties { - return types.NewProperties(). - Set("Name", r.data.Name). - Set("Type", r.data.Type) + return types.NewPropertiesFromStruct(r) } func (r *Route53ResourceRecordSet) String() string { - return ptr.ToString(r.data.Name) + return ptr.ToString(r.Name) } diff --git a/resources/route53-resource-record_test.go b/resources/route53-resource-record_test.go new file mode 100644 index 00000000..e8fc6d7e --- /dev/null +++ b/resources/route53-resource-record_test.go @@ -0,0 +1,49 @@ +package resources + +import ( + "testing" + + "github.com/gotidy/ptr" + "github.com/stretchr/testify/assert" + + "github.com/aws/aws-sdk-go/service/route53" +) + +func TestRoute53ResourceRecordSet_Properties(t *testing.T) { + cases := []struct { + name string + recordType string + }{ + { + name: "example.com", + recordType: "NS", + }, + { + name: "example.com", + recordType: "SOA", + }, + { + name: "subdomain.example.com", + recordType: "A", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + r := &Route53ResourceRecordSet{ + resourceRecordSet: &route53.ResourceRecordSet{ + Name: ptr.String(tc.name), + Type: ptr.String(tc.recordType), + }, + Name: ptr.String(tc.name), + Type: ptr.String(tc.recordType), + } + + got := r.Properties() + assert.Equal(t, tc.name, got.Get("Name")) + assert.Equal(t, tc.recordType, got.Get("Type")) + + assert.Equal(t, tc.name, r.String()) + }) + } +} diff --git a/resources/s3-access-grants-grant.go b/resources/s3-access-grants-grant.go new file mode 100644 index 00000000..bc5798df --- /dev/null +++ b/resources/s3-access-grants-grant.go @@ -0,0 +1,87 @@ +package resources + +import ( + "context" + "strings" + "time" + + "github.com/gotidy/ptr" + + "github.com/aws/aws-sdk-go-v2/service/s3control" + + "github.com/ekristen/libnuke/pkg/registry" + "github.com/ekristen/libnuke/pkg/resource" + "github.com/ekristen/libnuke/pkg/types" + + "github.com/ekristen/aws-nuke/v3/pkg/nuke" +) + +const S3AccessGrantsGrantResource = "S3AccessGrantsGrant" + +func init() { + registry.Register(®istry.Registration{ + Name: S3AccessGrantsGrantResource, + Scope: nuke.Account, + Resource: &S3AccessGrantsGrant{}, + Lister: &S3AccessGrantsGrantLister{}, + }) +} + +type S3AccessGrantsGrantLister struct{} + +func (l *S3AccessGrantsGrantLister) List(ctx context.Context, o interface{}) ([]resource.Resource, error) { + opts := o.(*nuke.ListerOpts) + svc := s3control.NewFromConfig(*opts.Config) + var resources []resource.Resource + + res, err := svc.ListAccessGrants(ctx, &s3control.ListAccessGrantsInput{ + AccountId: opts.AccountID, + }) + if err != nil { + if strings.Contains(err.Error(), "AccessGrantsInstanceNotExistsError") { + return resources, nil + } else { + return nil, err + } + } + + for _, p := range res.AccessGrantsList { + resources = append(resources, &S3AccessGrantsGrant{ + svc: svc, + accountID: opts.AccountID, + ID: p.AccessGrantId, + GrantScope: p.GrantScope, + GranteeType: ptr.String(string(p.Grantee.GranteeType)), + GranteeID: p.Grantee.GranteeIdentifier, + CreatedAt: p.CreatedAt, + }) + } + + return resources, nil +} + +type S3AccessGrantsGrant struct { + svc *s3control.Client + accountID *string + ID *string `description:"The ID of the access grant."` + GrantScope *string `description:"The scope of the access grant."` + GranteeType *string `description:"The type of the grantee, (e.g. IAM)."` + GranteeID *string `description:"The ARN of the grantee."` + CreatedAt *time.Time `description:"The date and time the access grant was created."` +} + +func (r *S3AccessGrantsGrant) Remove(ctx context.Context) error { + _, err := r.svc.DeleteAccessGrant(ctx, &s3control.DeleteAccessGrantInput{ + AccountId: r.accountID, + AccessGrantId: r.ID, + }) + return err +} + +func (r *S3AccessGrantsGrant) Properties() types.Properties { + return types.NewPropertiesFromStruct(r) +} + +func (r *S3AccessGrantsGrant) String() string { + return *r.ID +} diff --git a/resources/s3-access-grants-instance.go b/resources/s3-access-grants-instance.go new file mode 100644 index 00000000..05e9b0d8 --- /dev/null +++ b/resources/s3-access-grants-instance.go @@ -0,0 +1,73 @@ +package resources + +import ( + "context" + "time" + + "github.com/aws/aws-sdk-go-v2/service/s3control" + + "github.com/ekristen/libnuke/pkg/registry" + "github.com/ekristen/libnuke/pkg/resource" + "github.com/ekristen/libnuke/pkg/types" + + "github.com/ekristen/aws-nuke/v3/pkg/nuke" +) + +const S3AccessGrantsInstanceResource = "S3AccessGrantsInstance" + +func init() { + registry.Register(®istry.Registration{ + Name: S3AccessGrantsInstanceResource, + Scope: nuke.Account, + Resource: &S3AccessGrantsInstance{}, + Lister: &S3AccessGrantsInstanceLister{}, + }) +} + +type S3AccessGrantsInstanceLister struct{} + +func (l *S3AccessGrantsInstanceLister) List(ctx context.Context, o interface{}) ([]resource.Resource, error) { + opts := o.(*nuke.ListerOpts) + svc := s3control.NewFromConfig(*opts.Config) + var resources []resource.Resource + + res, err := svc.ListAccessGrantsInstances(ctx, &s3control.ListAccessGrantsInstancesInput{ + AccountId: opts.AccountID, + }) + if err != nil { + return nil, err + } + + for _, entity := range res.AccessGrantsInstancesList { + resources = append(resources, &S3AccessGrantsInstance{ + svc: svc, + accountID: opts.AccountID, + ID: entity.AccessGrantsInstanceId, + CreatedAt: entity.CreatedAt, + }) + } + + return resources, nil +} + +type S3AccessGrantsInstance struct { + svc *s3control.Client + accountID *string + ID *string `description:"The ID of the access grants instance."` + CreatedAt *time.Time `description:"The time the access grants instance was created."` +} + +func (r *S3AccessGrantsInstance) Remove(ctx context.Context) error { + _, err := r.svc.DeleteAccessGrantsInstance(ctx, &s3control.DeleteAccessGrantsInstanceInput{ + AccountId: r.accountID, + }) + return err +} + +func (r *S3AccessGrantsInstance) Properties() types.Properties { + return types.NewPropertiesFromStruct(r) +} + +func (r *S3AccessGrantsInstance) String() string { + return *r.ID +} diff --git a/resources/s3-access-grants-location.go b/resources/s3-access-grants-location.go new file mode 100644 index 00000000..c8ac0186 --- /dev/null +++ b/resources/s3-access-grants-location.go @@ -0,0 +1,81 @@ +package resources + +import ( + "context" + "strings" + "time" + + "github.com/aws/aws-sdk-go-v2/service/s3control" + + "github.com/ekristen/libnuke/pkg/registry" + "github.com/ekristen/libnuke/pkg/resource" + "github.com/ekristen/libnuke/pkg/types" + + "github.com/ekristen/aws-nuke/v3/pkg/nuke" +) + +const S3AccessGrantsLocationResource = "S3AccessGrantsLocation" + +func init() { + registry.Register(®istry.Registration{ + Name: S3AccessGrantsLocationResource, + Scope: nuke.Account, + Resource: &S3AccessGrantsLocation{}, + Lister: &S3AccessGrantsLocationLister{}, + }) +} + +type S3AccessGrantsLocationLister struct{} + +func (l *S3AccessGrantsLocationLister) List(ctx context.Context, o interface{}) ([]resource.Resource, error) { + opts := o.(*nuke.ListerOpts) + svc := s3control.NewFromConfig(*opts.Config) + var resources []resource.Resource + + res, err := svc.ListAccessGrantsLocations(ctx, &s3control.ListAccessGrantsLocationsInput{ + AccountId: opts.AccountID, + }) + if err != nil { + if strings.Contains(err.Error(), "AccessGrantsInstanceNotExistsError") { + return resources, nil + } else { + return nil, err + } + } + + for _, entity := range res.AccessGrantsLocationsList { + resources = append(resources, &S3AccessGrantsLocation{ + svc: svc, + accountID: opts.AccountID, + ID: entity.AccessGrantsLocationId, + LocationScope: entity.LocationScope, + CreatedAt: entity.CreatedAt, + }) + } + + return resources, nil +} + +type S3AccessGrantsLocation struct { + svc *s3control.Client + accountID *string + ID *string `description:"The ID of the access grants location."` + LocationScope *string `description:"The scope of the access grants location."` + CreatedAt *time.Time `description:"The time the access grants location was created."` +} + +func (r *S3AccessGrantsLocation) Remove(ctx context.Context) error { + _, err := r.svc.DeleteAccessGrantsLocation(ctx, &s3control.DeleteAccessGrantsLocationInput{ + AccessGrantsLocationId: r.ID, + AccountId: r.accountID, + }) + return err +} + +func (r *S3AccessGrantsLocation) Properties() types.Properties { + return types.NewPropertiesFromStruct(r) +} + +func (r *S3AccessGrantsLocation) String() string { + return *r.ID +} diff --git a/resources/s3-access-points.go b/resources/s3-access-point.go similarity index 57% rename from resources/s3-access-points.go rename to resources/s3-access-point.go index a94ba068..e5d58e3c 100644 --- a/resources/s3-access-points.go +++ b/resources/s3-access-point.go @@ -5,7 +5,6 @@ import ( "github.com/gotidy/ptr" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/s3control" "github.com/ekristen/libnuke/pkg/registry" @@ -46,15 +45,20 @@ func (l *S3AccessPointLister) List(_ context.Context, o interface{}) ([]resource for _, accessPoint := range resp.AccessPointList { resources = append(resources, &S3AccessPoint{ - svc: svc, - accountID: opts.AccountID, - accessPoint: accessPoint, + svc: svc, + accountID: opts.AccountID, + Name: accessPoint.Name, + ARN: accessPoint.AccessPointArn, + Alias: accessPoint.Alias, + Bucket: accessPoint.Bucket, + NetworkOrigin: accessPoint.NetworkOrigin, }) } if resp.NextToken == nil { break } + params.NextToken = resp.NextToken } @@ -62,30 +66,28 @@ func (l *S3AccessPointLister) List(_ context.Context, o interface{}) ([]resource } type S3AccessPoint struct { - svc *s3control.S3Control - accountID *string - accessPoint *s3control.AccessPoint + svc *s3control.S3Control + accountID *string + Name *string + ARN *string + Alias *string + Bucket *string + NetworkOrigin *string } -func (e *S3AccessPoint) Remove(_ context.Context) error { - _, err := e.svc.DeleteAccessPoint(&s3control.DeleteAccessPointInput{ - AccountId: e.accountID, - Name: aws.String(*e.accessPoint.Name), +func (r *S3AccessPoint) Remove(_ context.Context) error { + _, err := r.svc.DeleteAccessPoint(&s3control.DeleteAccessPointInput{ + AccountId: r.accountID, + Name: r.Name, }) return err } -func (e *S3AccessPoint) Properties() types.Properties { - properties := types.NewProperties() - properties.Set("AccessPointArn", e.accessPoint.AccessPointArn). - Set("Alias", e.accessPoint.Alias). - Set("Bucket", e.accessPoint.Bucket). - Set("Name", e.accessPoint.Name). - Set("NetworkOrigin", e.accessPoint.NetworkOrigin) - - return properties +func (r *S3AccessPoint) Properties() types.Properties { + return types.NewPropertiesFromStruct(r). + Set("AccessPointArn", r.ARN) // TODO(ek): this is an alias, should be deprecated for ARN } -func (e *S3AccessPoint) String() string { - return ptr.ToString(e.accessPoint.AccessPointArn) +func (r *S3AccessPoint) String() string { + return ptr.ToString(r.ARN) // TODO(ek): this should be the Name not the ARN } diff --git a/resources/s3-access-point_test.go b/resources/s3-access-point_test.go new file mode 100644 index 00000000..b7e119ed --- /dev/null +++ b/resources/s3-access-point_test.go @@ -0,0 +1,49 @@ +package resources + +import ( + "fmt" + "testing" + + "github.com/gotidy/ptr" + "github.com/stretchr/testify/assert" +) + +func TestS3AccessPointProperties(t *testing.T) { + tests := []struct { + accountID string + name string + alias string + bucket string + networkOrigin string + }{ + { + accountID: "123456789012", + name: "test-access-point", + alias: "some-alias", + bucket: "some-bucket", + networkOrigin: "some-network-origin", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + obj := &S3AccessPoint{ + accountID: ptr.String(tc.accountID), + ARN: ptr.String(fmt.Sprintf("arn:aws:s3:::%s:%s", tc.accountID, tc.name)), + Name: ptr.String(tc.name), + Alias: ptr.String(tc.alias), + Bucket: ptr.String(tc.bucket), + NetworkOrigin: ptr.String(tc.networkOrigin), + } + + got := obj.Properties() + assert.Equal(t, tc.name, got.Get("Name")) + assert.Equal(t, fmt.Sprintf("arn:aws:s3:::%s:%s", tc.accountID, tc.name), got.Get("AccessPointArn")) + assert.Equal(t, tc.alias, got.Get("Alias")) + assert.Equal(t, tc.bucket, got.Get("Bucket")) + assert.Equal(t, tc.networkOrigin, got.Get("NetworkOrigin")) + + assert.Equal(t, fmt.Sprintf("arn:aws:s3:::%s:%s", tc.accountID, tc.name), obj.String()) + }) + } +} diff --git a/resources/s3-bucket-helpers.go b/resources/s3-bucket-helpers.go new file mode 100644 index 00000000..6e3b4fcf --- /dev/null +++ b/resources/s3-bucket-helpers.go @@ -0,0 +1,172 @@ +package resources + +import ( + "context" + "time" + + "github.com/gotidy/ptr" + "github.com/sirupsen/logrus" + + "github.com/aws/aws-sdk-go-v2/service/s3" + s3types "github.com/aws/aws-sdk-go-v2/service/s3/types" + + "github.com/ekristen/aws-nuke/v3/pkg/awsmod" +) + +func bypassGovernanceRetention(input *s3.DeleteObjectsInput) { + input.BypassGovernanceRetention = ptr.Bool(true) +} + +type s3DeleteVersionListIterator struct { + Bucket *string + Paginator *s3.ListObjectVersionsPaginator + objects []s3types.ObjectVersion + lastNotify time.Time + BypassGovernanceRetention *bool + err error +} + +func newS3DeleteVersionListIterator( + svc *s3.Client, + input *s3.ListObjectVersionsInput, + bypass bool, + opts ...func(*s3DeleteVersionListIterator)) awsmod.BatchDeleteIterator { + iter := &s3DeleteVersionListIterator{ + Bucket: input.Bucket, + Paginator: s3.NewListObjectVersionsPaginator(svc, input), + BypassGovernanceRetention: ptr.Bool(bypass), + } + + for _, opt := range opts { + opt(iter) + } + + return iter +} + +// Next will use the S3API client to iterate through a list of objects. +func (iter *s3DeleteVersionListIterator) Next() bool { + if len(iter.objects) > 0 { + iter.objects = iter.objects[1:] + if len(iter.objects) > 0 { + return true + } + } + + if !iter.Paginator.HasMorePages() { + return false + } + + page, err := iter.Paginator.NextPage(context.TODO()) + if err != nil { + iter.err = err + return false + } + + iter.objects = page.Versions + for _, entry := range page.DeleteMarkers { + iter.objects = append(iter.objects, s3types.ObjectVersion{ + Key: entry.Key, + VersionId: entry.VersionId, + }) + } + + if len(iter.objects) > 500 && (iter.lastNotify.IsZero() || time.Since(iter.lastNotify) > 120*time.Second) { + logrus.Infof( + "S3Bucket: %s - empty bucket operation in progress, this could take a while, please be patient", + *iter.Bucket) + iter.lastNotify = time.Now().UTC() + } + + return len(iter.objects) > 0 +} + +// Err will return the last known error from Next. +func (iter *s3DeleteVersionListIterator) Err() error { + return iter.err +} + +// DeleteObject will return the current object to be deleted. +func (iter *s3DeleteVersionListIterator) DeleteObject() awsmod.BatchDeleteObject { + return awsmod.BatchDeleteObject{ + Object: &s3.DeleteObjectInput{ + Bucket: iter.Bucket, + Key: iter.objects[0].Key, + VersionId: iter.objects[0].VersionId, + BypassGovernanceRetention: iter.BypassGovernanceRetention, + }, + } +} + +type s3ObjectDeleteListIterator struct { + Bucket *string + Paginator *s3.ListObjectsV2Paginator + objects []s3types.Object + lastNotify time.Time + BypassGovernanceRetention bool + err error +} + +func newS3ObjectDeleteListIterator( + svc *s3.Client, + input *s3.ListObjectsV2Input, + bypass bool, + opts ...func(*s3ObjectDeleteListIterator)) awsmod.BatchDeleteIterator { + iter := &s3ObjectDeleteListIterator{ + Bucket: input.Bucket, + Paginator: s3.NewListObjectsV2Paginator(svc, input), + BypassGovernanceRetention: bypass, + } + + for _, opt := range opts { + opt(iter) + } + return iter +} + +// Next will use the S3API client to iterate through a list of objects. +func (iter *s3ObjectDeleteListIterator) Next() bool { + if len(iter.objects) > 0 { + iter.objects = iter.objects[1:] + if len(iter.objects) > 0 { + return true + } + } + + if !iter.Paginator.HasMorePages() { + return false + } + + page, err := iter.Paginator.NextPage(context.TODO()) + if err != nil { + iter.err = err + return false + } + + iter.objects = page.Contents + + if len(iter.objects) > 500 && (iter.lastNotify.IsZero() || time.Since(iter.lastNotify) > 120*time.Second) { + logrus.Infof( + "S3Bucket: %s - empty bucket operation in progress, this could take a while, please be patient", + *iter.Bucket) + iter.lastNotify = time.Now().UTC() + } + + return len(iter.objects) > 0 +} + +// Err will return the last known error from Next. +func (iter *s3ObjectDeleteListIterator) Err() error { + return iter.err +} + +// DeleteObject will return the current object to be deleted. +func (iter *s3ObjectDeleteListIterator) DeleteObject() awsmod.BatchDeleteObject { + return awsmod.BatchDeleteObject{ + Object: &s3.DeleteObjectInput{ + Bucket: iter.Bucket, + Key: iter.objects[0].Key, + BypassGovernanceRetention: ptr.Bool(iter.BypassGovernanceRetention), + }, + } +} diff --git a/resources/s3-bucket.go b/resources/s3-bucket.go index 96a99d1e..d264214e 100644 --- a/resources/s3-bucket.go +++ b/resources/s3-bucket.go @@ -9,7 +9,6 @@ import ( "github.com/gotidy/ptr" "github.com/sirupsen/logrus" - "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/s3" s3types "github.com/aws/aws-sdk-go-v2/service/s3/types" "github.com/aws/smithy-go" @@ -48,7 +47,7 @@ func (l *S3BucketLister) List(ctx context.Context, o interface{}) ([]resource.Re opts := o.(*nuke.ListerOpts) svc := s3.NewFromConfig(*opts.Config) - buckets, err := DescribeS3Buckets(ctx, svc) + buckets, err := DescribeS3Buckets(ctx, svc, opts) if err != nil { return nil, err } @@ -57,13 +56,13 @@ func (l *S3BucketLister) List(ctx context.Context, o interface{}) ([]resource.Re for _, bucket := range buckets { newBucket := &S3Bucket{ svc: svc, - name: aws.ToString(bucket.Name), - creationDate: aws.ToTime(bucket.CreationDate), - tags: make([]s3types.Tag, 0), + Name: bucket.Name, + CreationDate: bucket.CreationDate, + Tags: make([]s3types.Tag, 0), } lockCfg, err := svc.GetObjectLockConfiguration(ctx, &s3.GetObjectLockConfigurationInput{ - Bucket: &newBucket.name, + Bucket: newBucket.Name, }) if err != nil { // check if aws error is NoSuchObjectLockConfiguration @@ -92,7 +91,7 @@ func (l *S3BucketLister) List(ctx context.Context, o interface{}) ([]resource.Re continue } - newBucket.tags = tags.TagSet + newBucket.Tags = tags.TagSet resources = append(resources, newBucket) } @@ -105,32 +104,27 @@ type DescribeS3BucketsAPIClient interface { GetBucketLocation(context.Context, *s3.GetBucketLocationInput, ...func(*s3.Options)) (*s3.GetBucketLocationOutput, error) } -func DescribeS3Buckets(ctx context.Context, svc DescribeS3BucketsAPIClient) ([]s3types.Bucket, error) { - resp, err := svc.ListBuckets(ctx, nil) - if err != nil { - return nil, err +func DescribeS3Buckets(ctx context.Context, svc DescribeS3BucketsAPIClient, opts *nuke.ListerOpts) ([]s3types.Bucket, error) { + buckets := make([]s3types.Bucket, 0) + + params := &s3.ListBucketsInput{ + BucketRegion: ptr.String(opts.Region.Name), + MaxBuckets: ptr.Int32(100), } - buckets := make([]s3types.Bucket, 0) - for _, out := range resp.Buckets { - bucketLocationResponse, err := svc.GetBucketLocation(ctx, &s3.GetBucketLocationInput{Bucket: out.Name}) + for { + resp, err := svc.ListBuckets(ctx, params) if err != nil { - continue + return nil, err } - location := string(bucketLocationResponse.LocationConstraint) - if location == "" { - location = "us-east-1" - } + buckets = append(buckets, resp.Buckets...) - region := svc.Options().Region - if region == "" { - region = "us-east-1" + if resp.ContinuationToken == nil { + break } - if location == region { - buckets = append(buckets, out) - } + params.ContinuationToken = resp.ContinuationToken } return buckets, nil @@ -139,22 +133,22 @@ func DescribeS3Buckets(ctx context.Context, svc DescribeS3BucketsAPIClient) ([]s type S3Bucket struct { svc *s3.Client settings *libsettings.Setting - name string - creationDate time.Time - tags []s3types.Tag + Name *string + CreationDate *time.Time + Tags []s3types.Tag ObjectLock s3types.ObjectLockEnabled } func (r *S3Bucket) Remove(ctx context.Context) error { _, err := r.svc.DeleteBucketPolicy(ctx, &s3.DeleteBucketPolicyInput{ - Bucket: &r.name, + Bucket: r.Name, }) if err != nil { return err } _, err = r.svc.PutBucketLogging(ctx, &s3.PutBucketLoggingInput{ - Bucket: &r.name, + Bucket: r.Name, BucketLoggingStatus: &s3types.BucketLoggingStatus{}, }) if err != nil { @@ -177,7 +171,7 @@ func (r *S3Bucket) Remove(ctx context.Context) error { } _, err = r.svc.DeleteBucket(ctx, &s3.DeleteBucketInput{ - Bucket: &r.name, + Bucket: r.Name, }) return err @@ -188,12 +182,12 @@ func (r *S3Bucket) RemoveAllLegalHolds(ctx context.Context) error { return nil } - if r.ObjectLock == "" || r.ObjectLock != s3types.ObjectLockEnabledEnabled { + if r.ObjectLock != s3types.ObjectLockEnabledEnabled { return nil } params := &s3.ListObjectsV2Input{ - Bucket: &r.name, + Bucket: r.Name, } for { @@ -206,7 +200,7 @@ func (r *S3Bucket) RemoveAllLegalHolds(ctx context.Context) error { for _, obj := range res.Contents { _, err := r.svc.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{ - Bucket: &r.name, + Bucket: r.Name, Key: obj.Key, LegalHold: &s3types.ObjectLockLegalHold{Status: s3types.ObjectLockLegalHoldStatusOff}, }) @@ -225,7 +219,7 @@ func (r *S3Bucket) RemoveAllLegalHolds(ctx context.Context) error { func (r *S3Bucket) RemoveAllVersions(ctx context.Context) error { params := &s3.ListObjectVersionsInput{ - Bucket: &r.name, + Bucket: r.Name, } var setBypass bool @@ -242,7 +236,7 @@ func (r *S3Bucket) RemoveAllVersions(ctx context.Context) error { func (r *S3Bucket) RemoveAllObjects(ctx context.Context) error { params := &s3.ListObjectsV2Input{ - Bucket: &r.name, + Bucket: r.Name, } var setBypass bool @@ -262,176 +256,9 @@ func (r *S3Bucket) Settings(settings *libsettings.Setting) { } func (r *S3Bucket) Properties() types.Properties { - properties := types.NewProperties(). - Set("Name", r.name). - Set("CreationDate", r.creationDate.Format(time.RFC3339)). - Set("ObjectLock", r.ObjectLock) - - for _, tag := range r.tags { - properties.SetTag(tag.Key, tag.Value) - } - - return properties + return types.NewPropertiesFromStruct(r) } func (r *S3Bucket) String() string { - return fmt.Sprintf("s3://%s", r.name) -} - -func bypassGovernanceRetention(input *s3.DeleteObjectsInput) { - input.BypassGovernanceRetention = ptr.Bool(true) -} - -type s3DeleteVersionListIterator struct { - Bucket *string - Paginator *s3.ListObjectVersionsPaginator - objects []s3types.ObjectVersion - lastNotify time.Time - BypassGovernanceRetention *bool - err error -} - -func newS3DeleteVersionListIterator( - svc *s3.Client, - input *s3.ListObjectVersionsInput, - bypass bool, - opts ...func(*s3DeleteVersionListIterator)) awsmod.BatchDeleteIterator { - iter := &s3DeleteVersionListIterator{ - Bucket: input.Bucket, - Paginator: s3.NewListObjectVersionsPaginator(svc, input), - BypassGovernanceRetention: ptr.Bool(bypass), - } - - for _, opt := range opts { - opt(iter) - } - - return iter -} - -// Next will use the S3API client to iterate through a list of objects. -func (iter *s3DeleteVersionListIterator) Next() bool { - if len(iter.objects) > 0 { - iter.objects = iter.objects[1:] - if len(iter.objects) > 0 { - return true - } - } - - if !iter.Paginator.HasMorePages() { - return false - } - - page, err := iter.Paginator.NextPage(context.TODO()) - if err != nil { - iter.err = err - return false - } - - iter.objects = page.Versions - for _, entry := range page.DeleteMarkers { - iter.objects = append(iter.objects, s3types.ObjectVersion{ - Key: entry.Key, - VersionId: entry.VersionId, - }) - } - - if len(iter.objects) > 500 && (iter.lastNotify.IsZero() || time.Since(iter.lastNotify) > 120*time.Second) { - logrus.Infof( - "S3Bucket: %s - empty bucket operation in progress, this could take a while, please be patient", - *iter.Bucket) - iter.lastNotify = time.Now().UTC() - } - - return len(iter.objects) > 0 -} - -// Err will return the last known error from Next. -func (iter *s3DeleteVersionListIterator) Err() error { - return iter.err -} - -// DeleteObject will return the current object to be deleted. -func (iter *s3DeleteVersionListIterator) DeleteObject() awsmod.BatchDeleteObject { - return awsmod.BatchDeleteObject{ - Object: &s3.DeleteObjectInput{ - Bucket: iter.Bucket, - Key: iter.objects[0].Key, - VersionId: iter.objects[0].VersionId, - BypassGovernanceRetention: iter.BypassGovernanceRetention, - }, - } -} - -type s3ObjectDeleteListIterator struct { - Bucket *string - Paginator *s3.ListObjectsV2Paginator - objects []s3types.Object - lastNotify time.Time - BypassGovernanceRetention bool - err error -} - -func newS3ObjectDeleteListIterator( - svc *s3.Client, - input *s3.ListObjectsV2Input, - bypass bool, - opts ...func(*s3ObjectDeleteListIterator)) awsmod.BatchDeleteIterator { - iter := &s3ObjectDeleteListIterator{ - Bucket: input.Bucket, - Paginator: s3.NewListObjectsV2Paginator(svc, input), - BypassGovernanceRetention: bypass, - } - - for _, opt := range opts { - opt(iter) - } - return iter -} - -// Next will use the S3API client to iterate through a list of objects. -func (iter *s3ObjectDeleteListIterator) Next() bool { - if len(iter.objects) > 0 { - iter.objects = iter.objects[1:] - if len(iter.objects) > 0 { - return true - } - } - - if !iter.Paginator.HasMorePages() { - return false - } - - page, err := iter.Paginator.NextPage(context.TODO()) - if err != nil { - iter.err = err - return false - } - - iter.objects = page.Contents - - if len(iter.objects) > 500 && (iter.lastNotify.IsZero() || time.Since(iter.lastNotify) > 120*time.Second) { - logrus.Infof( - "S3Bucket: %s - empty bucket operation in progress, this could take a while, please be patient", - *iter.Bucket) - iter.lastNotify = time.Now().UTC() - } - - return len(iter.objects) > 0 -} - -// Err will return the last known error from Next. -func (iter *s3ObjectDeleteListIterator) Err() error { - return iter.err -} - -// DeleteObject will return the current object to be deleted. -func (iter *s3ObjectDeleteListIterator) DeleteObject() awsmod.BatchDeleteObject { - return awsmod.BatchDeleteObject{ - Object: &s3.DeleteObjectInput{ - Bucket: iter.Bucket, - Key: iter.objects[0].Key, - BypassGovernanceRetention: ptr.Bool(iter.BypassGovernanceRetention), - }, - } + return fmt.Sprintf("s3://%s", *r.Name) } diff --git a/resources/s3-bucket_test.go b/resources/s3-bucket_test.go index 71cd42a7..22bfc828 100644 --- a/resources/s3-bucket_test.go +++ b/resources/s3-bucket_test.go @@ -10,6 +10,7 @@ import ( "testing" "time" + "github.com/gotidy/ptr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" @@ -29,14 +30,14 @@ func (readSeekCloser) Close() error { return nil } type TestS3BucketSuite struct { suite.Suite - bucket string + bucket *string svc *s3.Client } func (suite *TestS3BucketSuite) SetupSuite() { var err error - suite.bucket = fmt.Sprintf("aws-nuke-testing-bucket-%d", time.Now().UnixNano()) + suite.bucket = ptr.String(fmt.Sprintf("aws-nuke-testing-bucket-%d", time.Now().UnixNano())) ctx := context.TODO() @@ -50,7 +51,7 @@ func (suite *TestS3BucketSuite) SetupSuite() { // Create the bucket _, err = suite.svc.CreateBucket(ctx, &s3.CreateBucketInput{ - Bucket: aws.String(suite.bucket), + Bucket: suite.bucket, CreateBucketConfiguration: &s3types.CreateBucketConfiguration{ LocationConstraint: s3types.BucketLocationConstraint("us-west-2"), }, @@ -61,7 +62,7 @@ func (suite *TestS3BucketSuite) SetupSuite() { // enable versioning _, err = suite.svc.PutBucketVersioning(ctx, &s3.PutBucketVersioningInput{ - Bucket: aws.String(suite.bucket), + Bucket: suite.bucket, VersioningConfiguration: &s3types.VersioningConfiguration{ Status: s3types.BucketVersioningStatusEnabled, }, @@ -72,7 +73,7 @@ func (suite *TestS3BucketSuite) SetupSuite() { // Set the object lock configuration to governance mode _, err = suite.svc.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{ - Bucket: aws.String(suite.bucket), + Bucket: suite.bucket, ObjectLockConfiguration: &s3types.ObjectLockConfiguration{ ObjectLockEnabled: s3types.ObjectLockEnabledEnabled, Rule: &s3types.ObjectLockRule{ @@ -89,7 +90,7 @@ func (suite *TestS3BucketSuite) SetupSuite() { // Create an object in the bucket _, err = suite.svc.PutObject(ctx, &s3.PutObjectInput{ - Bucket: aws.String(suite.bucket), + Bucket: suite.bucket, Key: aws.String("test-object"), Body: readSeekCloser{strings.NewReader("test content")}, ChecksumAlgorithm: s3types.ChecksumAlgorithmCrc32, @@ -101,7 +102,7 @@ func (suite *TestS3BucketSuite) SetupSuite() { func (suite *TestS3BucketSuite) TearDownSuite() { iterator := newS3DeleteVersionListIterator(suite.svc, &s3.ListObjectVersionsInput{ - Bucket: &suite.bucket, + Bucket: suite.bucket, }, true) if err := awsmod.NewBatchDeleteWithClient(suite.svc).Delete(context.TODO(), iterator, bypassGovernanceRetention); err != nil { if !strings.Contains(err.Error(), "NoSuchBucket") { @@ -110,7 +111,7 @@ func (suite *TestS3BucketSuite) TearDownSuite() { } iterator2 := newS3ObjectDeleteListIterator(suite.svc, &s3.ListObjectsV2Input{ - Bucket: &suite.bucket, + Bucket: suite.bucket, }, true) if err := awsmod.NewBatchDeleteWithClient(suite.svc).Delete(context.TODO(), iterator2, bypassGovernanceRetention); err != nil { if !strings.Contains(err.Error(), "NoSuchBucket") { @@ -119,7 +120,7 @@ func (suite *TestS3BucketSuite) TearDownSuite() { } _, err := suite.svc.DeleteBucket(context.TODO(), &s3.DeleteBucketInput{ - Bucket: aws.String(suite.bucket), + Bucket: suite.bucket, }) if err != nil { if !strings.Contains(err.Error(), "NoSuchBucket") { @@ -135,7 +136,7 @@ type TestS3BucketObjectLockSuite struct { func (suite *TestS3BucketObjectLockSuite) TestS3BucketObjectLock() { // Verify the object lock configuration result, err := suite.svc.GetObjectLockConfiguration(context.TODO(), &s3.GetObjectLockConfigurationInput{ - Bucket: aws.String(suite.bucket), + Bucket: suite.bucket, }) if err != nil { suite.T().Fatalf("failed to get object lock configuration, %v", err) @@ -150,7 +151,7 @@ func (suite *TestS3BucketObjectLockSuite) TestS3BucketRemove() { // Create the S3Bucket object bucket := &S3Bucket{ svc: suite.svc, - name: suite.bucket, + Name: suite.bucket, settings: &libsettings.Setting{}, } @@ -165,12 +166,12 @@ type TestS3BucketBypassGovernanceSuite struct { func (suite *TestS3BucketBypassGovernanceSuite) TestS3BucketRemoveWithBypass() { // Create the S3Bucket object bucket := &S3Bucket{ - svc: suite.svc, - name: suite.bucket, - ObjectLock: s3types.ObjectLockEnabledEnabled, + svc: suite.svc, settings: &libsettings.Setting{ "BypassGovernanceRetention": true, }, + Name: suite.bucket, + ObjectLock: s3types.ObjectLockEnabledEnabled, } err := bucket.Remove(context.TODO()) diff --git a/resources/s3-multipart-uploads.go b/resources/s3-multipart-upload.go similarity index 68% rename from resources/s3-multipart-uploads.go rename to resources/s3-multipart-upload.go index feda2d96..159a6eee 100644 --- a/resources/s3-multipart-uploads.go +++ b/resources/s3-multipart-upload.go @@ -2,10 +2,8 @@ package resources import ( "context" - "fmt" - "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/ekristen/libnuke/pkg/registry" @@ -34,7 +32,7 @@ func (l *S3MultipartUploadLister) List(ctx context.Context, o interface{}) ([]re resources := make([]resource.Resource, 0) - buckets, err := DescribeS3Buckets(ctx, svc) + buckets, err := DescribeS3Buckets(ctx, svc, opts) if err != nil { return nil, err } @@ -57,9 +55,9 @@ func (l *S3MultipartUploadLister) List(ctx context.Context, o interface{}) ([]re resources = append(resources, &S3MultipartUpload{ svc: svc, - bucket: aws.ToString(bucket.Name), - key: *upload.Key, - uploadID: *upload.UploadId, + Bucket: bucket.Name, + Key: upload.Key, + UploadID: upload.UploadId, }) } @@ -77,19 +75,19 @@ func (l *S3MultipartUploadLister) List(ctx context.Context, o interface{}) ([]re type S3MultipartUpload struct { svc *s3.Client - bucket string - key string - uploadID string + Bucket *string + Key *string + UploadID *string } -func (e *S3MultipartUpload) Remove(ctx context.Context) error { +func (r *S3MultipartUpload) Remove(ctx context.Context) error { params := &s3.AbortMultipartUploadInput{ - Bucket: &e.bucket, - Key: &e.key, - UploadId: &e.uploadID, + Bucket: r.Bucket, + Key: r.Key, + UploadId: r.UploadID, } - _, err := e.svc.AbortMultipartUpload(ctx, params) + _, err := r.svc.AbortMultipartUpload(ctx, params) if err != nil { return err } @@ -97,13 +95,10 @@ func (e *S3MultipartUpload) Remove(ctx context.Context) error { return nil } -func (e *S3MultipartUpload) Properties() types.Properties { - return types.NewProperties(). - Set("Bucket", e.bucket). - Set("Key", e.key). - Set("UploadID", e.uploadID) +func (r *S3MultipartUpload) Properties() types.Properties { + return types.NewPropertiesFromStruct(r) } -func (e *S3MultipartUpload) String() string { - return fmt.Sprintf("s3://%s/%s#%s", e.bucket, e.key, e.uploadID) +func (r *S3MultipartUpload) String() string { + return fmt.Sprintf("s3://%s/%s#%s", *r.Bucket, *r.Key, *r.UploadID) } diff --git a/resources/s3-multipart-upload_test.go b/resources/s3-multipart-upload_test.go new file mode 100644 index 00000000..9cbe404b --- /dev/null +++ b/resources/s3-multipart-upload_test.go @@ -0,0 +1,41 @@ +package resources + +import ( + "fmt" + "testing" + + "github.com/gotidy/ptr" + "github.com/stretchr/testify/assert" +) + +func TestS3MultipartUploadProperties(t *testing.T) { + tests := []struct { + bucket string + key string + uploadID string + }{ + { + bucket: "test-bucket", + key: "test-key", + uploadID: "test-upload-id", + }, + } + + for _, test := range tests { + t.Run(test.bucket, func(t *testing.T) { + obj := &S3MultipartUpload{ + Bucket: ptr.String(test.bucket), + Key: ptr.String(test.key), + UploadID: ptr.String(test.uploadID), + } + + got := obj.Properties() + assert.Equal(t, test.bucket, got.Get("Bucket")) + assert.Equal(t, test.key, got.Get("Key")) + assert.Equal(t, test.uploadID, got.Get("UploadID")) + + uri := fmt.Sprintf("s3://%s/%s#%s", test.bucket, test.key, test.uploadID) + assert.Equal(t, uri, obj.String()) + }) + } +} diff --git a/resources/s3-objects.go b/resources/s3-object.go similarity index 57% rename from resources/s3-objects.go rename to resources/s3-object.go index bf89833e..e5ca6c3b 100644 --- a/resources/s3-objects.go +++ b/resources/s3-object.go @@ -2,13 +2,11 @@ package resources import ( "context" - "fmt" "time" "github.com/gotidy/ptr" - "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/ekristen/libnuke/pkg/registry" @@ -37,7 +35,7 @@ func (l *S3ObjectLister) List(ctx context.Context, o interface{}) ([]resource.Re resources := make([]resource.Resource, 0) - buckets, err := DescribeS3Buckets(ctx, svc) + buckets, err := DescribeS3Buckets(ctx, svc, opts) if err != nil { return nil, err } @@ -60,11 +58,11 @@ func (l *S3ObjectLister) List(ctx context.Context, o interface{}) ([]resource.Re resources = append(resources, &S3Object{ svc: svc, - bucket: aws.ToString(bucket.Name), - creationDate: aws.ToTime(bucket.CreationDate), - key: *out.Key, - versionID: out.VersionId, - latest: ptr.ToBool(out.IsLatest), + Bucket: bucket.Name, + CreationDate: bucket.CreationDate, + Key: out.Key, + VersionID: out.VersionId, + IsLatest: out.IsLatest, }) } @@ -75,11 +73,11 @@ func (l *S3ObjectLister) List(ctx context.Context, o interface{}) ([]resource.Re resources = append(resources, &S3Object{ svc: svc, - bucket: aws.ToString(bucket.Name), - creationDate: aws.ToTime(bucket.CreationDate), - key: *out.Key, - versionID: out.VersionId, - latest: ptr.ToBool(out.IsLatest), + Bucket: bucket.Name, + CreationDate: bucket.CreationDate, + Key: out.Key, + VersionID: out.VersionId, + IsLatest: out.IsLatest, }) } @@ -98,21 +96,21 @@ func (l *S3ObjectLister) List(ctx context.Context, o interface{}) ([]resource.Re type S3Object struct { svc *s3.Client - bucket string - creationDate time.Time - key string - versionID *string - latest bool + Bucket *string + CreationDate *time.Time + Key *string + VersionID *string + IsLatest *bool } -func (e *S3Object) Remove(ctx context.Context) error { +func (r *S3Object) Remove(ctx context.Context) error { params := &s3.DeleteObjectInput{ - Bucket: &e.bucket, - Key: &e.key, - VersionId: e.versionID, + Bucket: r.Bucket, + Key: r.Key, + VersionId: r.VersionID, } - _, err := e.svc.DeleteObject(ctx, params) + _, err := r.svc.DeleteObject(ctx, params) if err != nil { return err } @@ -120,18 +118,13 @@ func (e *S3Object) Remove(ctx context.Context) error { return nil } -func (e *S3Object) Properties() types.Properties { - return types.NewProperties(). - Set("Bucket", e.bucket). - Set("Key", e.key). - Set("VersionID", e.versionID). - Set("IsLatest", e.latest). - Set("CreationDate", e.creationDate) +func (r *S3Object) Properties() types.Properties { + return types.NewPropertiesFromStruct(r) } -func (e *S3Object) String() string { - if e.versionID != nil && *e.versionID != "null" && !e.latest { - return fmt.Sprintf("s3://%s/%s#%s", e.bucket, e.key, *e.versionID) +func (r *S3Object) String() string { + if r.VersionID != nil && *r.VersionID != "null" && !ptr.ToBool(r.IsLatest) { + return fmt.Sprintf("s3://%s/%s#%s", *r.Bucket, *r.Key, *r.VersionID) } - return fmt.Sprintf("s3://%s/%s", e.bucket, e.key) + return fmt.Sprintf("s3://%s/%s", *r.Bucket, *r.Key) } diff --git a/resources/s3-object_test.go b/resources/s3-object_test.go new file mode 100644 index 00000000..e196af3f --- /dev/null +++ b/resources/s3-object_test.go @@ -0,0 +1,65 @@ +package resources + +import ( + "fmt" + "testing" + "time" + + "github.com/gotidy/ptr" + "github.com/stretchr/testify/assert" +) + +func TestS3ObjectProperties(t *testing.T) { + tests := []struct { + bucket string + key string + creationDate time.Time + versionID string + isLatest bool + }{ + { + bucket: "test-bucket", + key: "test-key", + creationDate: time.Now(), + versionID: "null", + isLatest: true, + }, + { + bucket: "test-bucket", + key: "test-key", + creationDate: time.Now(), + versionID: "test-version-id", + isLatest: false, + }, + } + + for _, test := range tests { + t.Run(test.bucket, func(t *testing.T) { + obj := &S3Object{ + Bucket: ptr.String(test.bucket), + Key: ptr.String(test.key), + VersionID: ptr.String(test.versionID), + CreationDate: ptr.Time(test.creationDate), + IsLatest: ptr.Bool(test.isLatest), + } + + got := obj.Properties() + assert.Equal(t, test.bucket, got.Get("Bucket")) + assert.Equal(t, test.key, got.Get("Key")) + assert.Equal(t, test.versionID, got.Get("VersionID")) + assert.Equal(t, test.creationDate.Format(time.RFC3339), got.Get("CreationDate")) + + if test.isLatest { + assert.Equal(t, "true", got.Get("IsLatest")) + } else { + assert.Equal(t, "false", got.Get("IsLatest")) + } + + uri := fmt.Sprintf("s3://%s/%s", test.bucket, test.key) + if test.versionID != "" && test.versionID != "null" && !test.isLatest { + uri = fmt.Sprintf("%s#%s", uri, test.versionID) + } + assert.Equal(t, uri, obj.String()) + }) + } +} diff --git a/resources/sagemaker-algorithms.go b/resources/sagemaker-algorithms.go new file mode 100644 index 00000000..42b08bdd --- /dev/null +++ b/resources/sagemaker-algorithms.go @@ -0,0 +1,73 @@ +package resources + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/sagemaker" + + "github.com/ekristen/aws-nuke/v3/pkg/nuke" + "github.com/ekristen/libnuke/pkg/registry" + "github.com/ekristen/libnuke/pkg/resource" + "github.com/ekristen/libnuke/pkg/types" +) + +const SageMakerAlgorithmResource = "SageMakerAlgorithm" + +func init() { + registry.Register(®istry.Registration{ + Name: SageMakerAlgorithmResource, + Scope: nuke.Account, + Resource: &SageMakerAlgorithm{}, + Lister: &SageMakerAlgorithmLister{}, + }) +} + +type SageMakerAlgorithmLister struct{} + +type SageMakerAlgorithm struct { + svc *sagemaker.Client + algorithmName *string +} + +func (l *SageMakerAlgorithmLister) List(ctx context.Context, o interface{}) ([]resource.Resource, error) { + opts := o.(*nuke.ListerOpts) + svc := sagemaker.NewFromConfig(*opts.Config) + + resources := make([]resource.Resource, 0) + params := &sagemaker.ListAlgorithmsInput{ + MaxResults: aws.Int32(30), + } + for { + resp, err := svc.ListAlgorithms(ctx, params) + if err != nil { + return nil, err + } + for _, algorithm := range resp.AlgorithmSummaryList { + resources = append(resources, &SageMakerAlgorithm{ + svc: svc, + algorithmName: algorithm.AlgorithmName, + }) + } + if resp.NextToken == nil { + break + } + params.NextToken = resp.NextToken + } + return resources, nil +} + +func (f *SageMakerAlgorithm) Remove(ctx context.Context) error { + _, err := f.svc.DeleteAlgorithm(ctx, &sagemaker.DeleteAlgorithmInput{ + AlgorithmName: f.algorithmName, + }) + return err +} + +func (f *SageMakerAlgorithm) String() string { + return *f.algorithmName +} + +func (f *SageMakerAlgorithm) Properties() types.Properties { + return types.NewPropertiesFromStruct(f) +} diff --git a/resources/sagemaker-trainingjobs.go b/resources/sagemaker-trainingjobs.go new file mode 100644 index 00000000..334e6bcc --- /dev/null +++ b/resources/sagemaker-trainingjobs.go @@ -0,0 +1,74 @@ +package resources + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/sagemaker" + "github.com/ekristen/aws-nuke/v3/pkg/nuke" + "github.com/ekristen/libnuke/pkg/registry" + "github.com/ekristen/libnuke/pkg/resource" + "github.com/ekristen/libnuke/pkg/types" +) + +const SageMakerTrainingJobListerResource = "SageMakerTrainingJobLister" + +func init() { + registry.Register(®istry.Registration{ + Name: SageMakerTrainingJobListerResource, + Scope: nuke.Account, + Resource: &SageMakerTrainingJobLister{}, + Lister: &SageMakerTrainingJobLister{}, + }) +} + +type SageMakerTrainingJobLister struct{} + +type SageMakerTrainingJob struct { + svc *sagemaker.Client + trainingJobName *string +} + +func (l *SageMakerTrainingJobLister) List(ctx context.Context, o interface{}) ([]resource.Resource, error) { + opts := o.(*nuke.ListerOpts) + svc := sagemaker.NewFromConfig(*opts.Config) + + resources := make([]resource.Resource, 0) + params := &sagemaker.ListTrainingJobsInput{ + MaxResults: aws.Int32(30), + } + for { + resp, err := svc.ListTrainingJobs(ctx, params) + if err != nil { + return nil, err + } + for _, trainingJob := range resp.TrainingJobSummaries { + resources = append(resources, &SageMakerTrainingJob{ + svc: svc, + trainingJobName: trainingJob.TrainingJobName, + }) + } + if resp.NextToken == nil { + break + } + params.NextToken = resp.NextToken + } + return resources, nil +} + +func (f *SageMakerTrainingJob) Remove(ctx context.Context) error { + _, err := f.svc.StopTrainingJob(ctx, &sagemaker.StopTrainingJobInput{ + TrainingJobName: f.trainingJobName, + }) + return err +} + +func (f *SageMakerTrainingJob) String() string { + return *f.trainingJobName +} + +func (f *SageMakerTrainingJob) Properties() types.Properties { + properties := types.NewProperties() + properties.Set("TrainingJobName", f.trainingJobName) + return properties +} diff --git a/resources/sfn-state-machine.go b/resources/sfn-state-machine.go new file mode 100644 index 00000000..20bbf4e8 --- /dev/null +++ b/resources/sfn-state-machine.go @@ -0,0 +1,100 @@ +package resources + +import ( + "context" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/sfn" + + "github.com/ekristen/libnuke/pkg/registry" + "github.com/ekristen/libnuke/pkg/resource" + "github.com/ekristen/libnuke/pkg/types" + + "github.com/ekristen/aws-nuke/v3/pkg/nuke" +) + +const SFNStateMachineResource = "SFNStateMachine" + +func init() { + registry.Register(®istry.Registration{ + Name: SFNStateMachineResource, + Scope: nuke.Account, + Resource: &SFNStateMachine{}, + Lister: &SFNStateMachineLister{}, + }) +} + +type SFNStateMachineLister struct{} + +func (l *SFNStateMachineLister) List(_ context.Context, o interface{}) ([]resource.Resource, error) { + opts := o.(*nuke.ListerOpts) + + svc := sfn.New(opts.Session) + resources := make([]resource.Resource, 0) + + params := &sfn.ListStateMachinesInput{ + MaxResults: aws.Int64(100), + } + + for { + output, err := svc.ListStateMachines(params) + if err != nil { + return nil, err + } + + for _, stateMachine := range output.StateMachines { + var resourceTags []*sfn.Tag + tags, err := svc.ListTagsForResource(&sfn.ListTagsForResourceInput{ + ResourceArn: stateMachine.StateMachineArn, + }) + if err != nil { + opts.Logger.WithError(err).Error("unable to list state machine tags") + } else { + resourceTags = tags.Tags + } + + resources = append(resources, &SFNStateMachine{ + svc: svc, + ARN: stateMachine.StateMachineArn, + Name: stateMachine.Name, + Type: stateMachine.Type, + CreationDate: stateMachine.CreationDate, + Tags: resourceTags, + }) + } + + if output.NextToken == nil { + break + } + + params.NextToken = output.NextToken + } + + return resources, nil +} + +type SFNStateMachine struct { + svc *sfn.SFN + ARN *string `description:"The Amazon Resource Name (ARN) that identifies the state machine."` + Name *string `description:"The name of the state machine."` + Type *string `description:"The type of the state machine."` + CreationDate *time.Time `description:"The date the state machine was created."` + Tags []*sfn.Tag `description:"The tags associated with the state machine."` +} + +func (r *SFNStateMachine) Remove(_ context.Context) error { + _, err := r.svc.DeleteStateMachine(&sfn.DeleteStateMachineInput{ + StateMachineArn: r.ARN, + }) + + return err +} + +func (r *SFNStateMachine) Properties() types.Properties { + return types.NewPropertiesFromStruct(r) +} + +func (r *SFNStateMachine) String() string { + return *r.ARN +} diff --git a/resources/sfn-statemachines.go b/resources/sfn-statemachines.go deleted file mode 100644 index ca38e0bd..00000000 --- a/resources/sfn-statemachines.go +++ /dev/null @@ -1,76 +0,0 @@ -package resources - -import ( - "context" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/sfn" - - "github.com/ekristen/libnuke/pkg/registry" - "github.com/ekristen/libnuke/pkg/resource" - - "github.com/ekristen/aws-nuke/v3/pkg/nuke" -) - -const SFNStateMachineResource = "SFNStateMachine" - -func init() { - registry.Register(®istry.Registration{ - Name: SFNStateMachineResource, - Scope: nuke.Account, - Resource: &SFNStateMachine{}, - Lister: &SFNStateMachineLister{}, - }) -} - -type SFNStateMachineLister struct{} - -func (l *SFNStateMachineLister) List(_ context.Context, o interface{}) ([]resource.Resource, error) { - opts := o.(*nuke.ListerOpts) - - svc := sfn.New(opts.Session) - resources := make([]resource.Resource, 0) - - params := &sfn.ListStateMachinesInput{ - MaxResults: aws.Int64(100), - } - - for { - output, err := svc.ListStateMachines(params) - if err != nil { - return nil, err - } - - for _, stateMachine := range output.StateMachines { - resources = append(resources, &SFNStateMachine{ - svc: svc, - ARN: stateMachine.StateMachineArn, - }) - } - - if output.NextToken == nil { - break - } - - params.NextToken = output.NextToken - } - - return resources, nil -} - -type SFNStateMachine struct { - svc *sfn.SFN - ARN *string -} - -func (f *SFNStateMachine) Remove(_ context.Context) error { - _, err := f.svc.DeleteStateMachine(&sfn.DeleteStateMachineInput{ - StateMachineArn: f.ARN, - }) - - return err -} - -func (f *SFNStateMachine) String() string { - return *f.ARN -} diff --git a/resources/ssmquicksetup-configuration-manager.go b/resources/ssmquicksetup-configuration-manager.go new file mode 100644 index 00000000..60ef9283 --- /dev/null +++ b/resources/ssmquicksetup-configuration-manager.go @@ -0,0 +1,82 @@ +package resources + +import ( + "context" + "strings" + + "github.com/gotidy/ptr" + + "github.com/aws/aws-sdk-go-v2/service/ssmquicksetup" + + "github.com/ekristen/libnuke/pkg/registry" + "github.com/ekristen/libnuke/pkg/resource" + "github.com/ekristen/libnuke/pkg/types" + + "github.com/ekristen/aws-nuke/v3/pkg/nuke" +) + +const SSMQuickSetupConfigurationManagerResource = "SSMQuickSetupConfigurationManager" + +func init() { + registry.Register(®istry.Registration{ + Name: SSMQuickSetupConfigurationManagerResource, + Scope: nuke.Account, + Resource: &SSMQuickSetupConfigurationManager{}, + Lister: &SSMQuickSetupConfigurationManagerLister{}, + }) +} + +type SSMQuickSetupConfigurationManagerLister struct{} + +func (l *SSMQuickSetupConfigurationManagerLister) List(ctx context.Context, o interface{}) ([]resource.Resource, error) { + opts := o.(*nuke.ListerOpts) + svc := ssmquicksetup.NewFromConfig(*opts.Config) + var resources []resource.Resource + + res, err := svc.ListConfigurationManagers(ctx, &ssmquicksetup.ListConfigurationManagersInput{}) + if err != nil { + return nil, err + } + + for _, p := range res.ConfigurationManagersList { + resources = append(resources, &SSMQuickSetupConfigurationManager{ + svc: svc, + ARN: p.ManagerArn, + Name: p.Name, + }) + } + + return resources, nil +} + +type SSMQuickSetupConfigurationManager struct { + svc *ssmquicksetup.Client + ARN *string + Name *string +} + +// GetName returns the name of the resource or the last part of the ARN if not set so that the stringer resource has +// a value to display +func (r *SSMQuickSetupConfigurationManager) GetName() string { + if ptr.ToString(r.Name) != "" { + return ptr.ToString(r.Name) + } + + parts := strings.Split(ptr.ToString(r.ARN), "/") + return parts[len(parts)-1] +} + +func (r *SSMQuickSetupConfigurationManager) Remove(ctx context.Context) error { + _, err := r.svc.DeleteConfigurationManager(ctx, &ssmquicksetup.DeleteConfigurationManagerInput{ + ManagerArn: r.ARN, + }) + return err +} + +func (r *SSMQuickSetupConfigurationManager) Properties() types.Properties { + return types.NewPropertiesFromStruct(r) +} + +func (r *SSMQuickSetupConfigurationManager) String() string { + return r.GetName() +} diff --git a/resources/transfer-web-app.go b/resources/transfer-web-app.go new file mode 100644 index 00000000..37fa19dd --- /dev/null +++ b/resources/transfer-web-app.go @@ -0,0 +1,66 @@ +package resources + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/service/transfer" + + "github.com/ekristen/libnuke/pkg/registry" + "github.com/ekristen/libnuke/pkg/resource" + "github.com/ekristen/libnuke/pkg/types" + + "github.com/ekristen/aws-nuke/v3/pkg/nuke" +) + +const TransferWebAppResource = "TransferWebApp" + +func init() { + registry.Register(®istry.Registration{ + Name: TransferWebAppResource, + Scope: nuke.Account, + Resource: &TransferWebApp{}, + Lister: &TransferWebAppLister{}, + }) +} + +type TransferWebAppLister struct{} + +func (l *TransferWebAppLister) List(ctx context.Context, o interface{}) ([]resource.Resource, error) { + opts := o.(*nuke.ListerOpts) + svc := transfer.NewFromConfig(*opts.Config) + var resources []resource.Resource + + res, err := svc.ListWebApps(ctx, &transfer.ListWebAppsInput{}) + if err != nil { + return nil, err + } + + for _, entry := range res.WebApps { + resources = append(resources, &TransferWebApp{ + svc: svc, + ID: entry.WebAppId, + }) + } + + return resources, nil +} + +type TransferWebApp struct { + svc *transfer.Client + ID *string +} + +func (r *TransferWebApp) Remove(ctx context.Context) error { + _, err := r.svc.DeleteWebApp(ctx, &transfer.DeleteWebAppInput{ + WebAppId: r.ID, + }) + return err +} + +func (r *TransferWebApp) Properties() types.Properties { + return types.NewPropertiesFromStruct(r) +} + +func (r *TransferWebApp) String() string { + return *r.ID +} diff --git a/resources/wafregional-rules.go b/resources/wafregional-rules.go index a294d368..dd68e597 100644 --- a/resources/wafregional-rules.go +++ b/resources/wafregional-rules.go @@ -3,9 +3,8 @@ package resources import ( "context" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/waf" - "github.com/aws/aws-sdk-go/service/wafregional" + "github.com/aws/aws-sdk-go-v2/service/wafregional" + wafregionalTypes "github.com/aws/aws-sdk-go-v2/service/wafregional/types" "github.com/ekristen/libnuke/pkg/registry" "github.com/ekristen/libnuke/pkg/resource" @@ -27,26 +26,29 @@ func init() { type WAFRegionalRuleLister struct{} -func (l *WAFRegionalRuleLister) List(_ context.Context, o interface{}) ([]resource.Resource, error) { +func (l *WAFRegionalRuleLister) List(ctx context.Context, o interface{}) ([]resource.Resource, error) { opts := o.(*nuke.ListerOpts) - svc := wafregional.New(opts.Session) + svc := wafregional.NewFromConfig(*opts.Config) resources := make([]resource.Resource, 0) - params := &waf.ListRulesInput{ - Limit: aws.Int64(50), + params := &wafregional.ListRulesInput{ + Limit: 50, } for { - resp, err := svc.ListRules(params) + resp, err := svc.ListRules(ctx, params) if err != nil { return nil, err } for _, rule := range resp.Rules { - ruleResp, _ := svc.GetRule(&waf.GetRuleInput{ + ruleResp, err := svc.GetRule(ctx, &wafregional.GetRuleInput{ RuleId: rule.RuleId, }) + if err != nil { + return nil, err + } resources = append(resources, &WAFRegionalRule{ svc: svc, ID: rule.RuleId, @@ -66,42 +68,44 @@ func (l *WAFRegionalRuleLister) List(_ context.Context, o interface{}) ([]resour } type WAFRegionalRule struct { - svc *wafregional.WAFRegional + svc *wafregional.Client ID *string name *string - rule *waf.Rule + rule *wafregionalTypes.Rule } -func (f *WAFRegionalRule) Remove(_ context.Context) error { - tokenOutput, err := f.svc.GetChangeToken(&waf.GetChangeTokenInput{}) +func (f *WAFRegionalRule) Remove(ctx context.Context) error { + tokenOutput, err := f.svc.GetChangeToken(ctx, &wafregional.GetChangeTokenInput{}) if err != nil { return err } - var ruleUpdates []*waf.RuleUpdate + ruleUpdates := []wafregionalTypes.RuleUpdate{} for _, predicate := range f.rule.Predicates { - ruleUpdates = append(ruleUpdates, &waf.RuleUpdate{ - Action: aws.String(waf.ChangeActionDelete), - Predicate: predicate, + ruleUpdates = append(ruleUpdates, wafregionalTypes.RuleUpdate{ + Action: wafregionalTypes.ChangeActionDelete, + Predicate: &predicate, }) } - _, err = f.svc.UpdateRule(&waf.UpdateRuleInput{ - ChangeToken: tokenOutput.ChangeToken, - RuleId: f.ID, - Updates: ruleUpdates, - }) + if len(ruleUpdates) > 0 { + _, err = f.svc.UpdateRule(ctx, &wafregional.UpdateRuleInput{ + ChangeToken: tokenOutput.ChangeToken, + RuleId: f.ID, + Updates: ruleUpdates, + }) - if err != nil { - return err + if err != nil { + return err + } } - tokenOutput, err = f.svc.GetChangeToken(&waf.GetChangeTokenInput{}) + tokenOutput, err = f.svc.GetChangeToken(ctx, &wafregional.GetChangeTokenInput{}) if err != nil { return err } - _, err = f.svc.DeleteRule(&waf.DeleteRuleInput{ + _, err = f.svc.DeleteRule(ctx, &wafregional.DeleteRuleInput{ RuleId: f.ID, ChangeToken: tokenOutput.ChangeToken, }) diff --git a/tools/create-resource/main.go b/tools/create-resource/main.go index 148b42ec..bae0c114 100644 --- a/tools/create-resource/main.go +++ b/tools/create-resource/main.go @@ -9,6 +9,8 @@ import ( "golang.org/x/text/cases" "golang.org/x/text/language" + + "github.com/iancoleman/strcase" ) const resourceTemplate = `package resources @@ -16,8 +18,7 @@ const resourceTemplate = `package resources import ( "context" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/{{.Service}}" + "github.com/aws/aws-sdk-go-v2/service/{{.Service}}" "github.com/ekristen/libnuke/pkg/resource" "github.com/ekristen/libnuke/pkg/registry" @@ -39,22 +40,22 @@ func init() { type {{.Combined}}Lister struct{} -func (l *{{.Combined}}Lister) List(_ context.Context, o interface{}) ([]resource.Resource, error) { +func (l *{{.Combined}}Lister) List(ctx context.Context, o interface{}) ([]resource.Resource, error) { opts := o.(*nuke.ListerOpts) - svc := {{.Service}}.New(opts.Session) + svc := {{.Service}}.NewFromConfig(*opts.Config) var resources []resource.Resource // NOTE: you might have to modify the code below to actually work, this currently does not // inspect the aws sdk instead is a jumping off point - res, err := svc.List{{.ResourceTypeTitle}}s(&{{.Service}}.List{{.ResourceTypeTitle}}sInput{}) + res, err := svc.List{{.ResourceTypeTitle}}s(ctx, &{{.Service}}.List{{.ResourceTypeTitle}}sInput{}) if err != nil { return nil, err } - for _, p := range res.{{.ResourceTypeTitle}}s { + for _, p := range res.{{.ResourceTypeTitle}}sList { resources = append(resources, &{{.Combined}}{ svc: svc, - ID: p.Id, + ID: p.{{.ResourceTypeTitle}}Id, Tags: p.Tags, }) } @@ -63,14 +64,14 @@ func (l *{{.Combined}}Lister) List(_ context.Context, o interface{}) ([]resource } type {{.Combined}} struct { - svc *{{.Service}}.{{.ServiceTitle}} + svc *{{.Service}}.Client ID *string Tags []*{{.Service}}.Tag } -func (r *{{.Combined}}) Remove(_ context.Context) error { - _, err := r.svc.Delete{{.ResourceTypeTitle}}(&{{.Service}}.Delete{{.ResourceTypeTitle}}Input{ - {{.ResourceTypeTitle}}Id: r.id, +func (r *{{.Combined}}) Remove(ctx context.Context) error { + _, err := r.svc.Delete{{.ResourceTypeTitle}}(ctx, &{{.Service}}.Delete{{.ResourceTypeTitle}}Input{ + {{.ResourceTypeTitle}}Id: r.ID, }) return err } @@ -107,8 +108,8 @@ func main() { Service: strings.ToLower(service), ServiceTitle: caser.String(service), ResourceType: resourceType, - ResourceTypeTitle: caser.String(resourceType), - Combined: fmt.Sprintf("%s%s", caser.String(service), caser.String(resourceType)), + ResourceTypeTitle: strcase.ToCamel(resourceType), + Combined: fmt.Sprintf("%s%s", caser.String(service), strcase.ToCamel(resourceType)), } tmpl, err := template.New("resource").Parse(resourceTemplate)