Skip to content

Commit 4d74f1c

Browse files
committed
adding ResourceShares support
1 parent 5b225c5 commit 4d74f1c

9 files changed

Lines changed: 293 additions & 3 deletions

File tree

aws/resource_registry.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ func getRegisteredRegionalResources() []resources.AwsResource {
136136
resources.NewRdsParameterGroup(),
137137
resources.NewRedshiftClusters(),
138138
resources.NewRedshiftSnapshotCopyGrants(),
139+
resources.NewResourceShares(),
139140
resources.NewS3AccessPoints(),
140141
resources.NewS3ObjectLambdaAccessPoints(),
141142
resources.NewSageMakerNotebookInstances(),

aws/resources/resource_share.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package resources
2+
3+
import (
4+
"context"
5+
6+
"github.com/aws/aws-sdk-go-v2/aws"
7+
"github.com/aws/aws-sdk-go-v2/service/ram"
8+
"github.com/aws/aws-sdk-go-v2/service/ram/types"
9+
"github.com/gruntwork-io/cloud-nuke/config"
10+
"github.com/gruntwork-io/cloud-nuke/resource"
11+
)
12+
13+
// RAMResourceShareAPI defines the interface for RAM Resource Share operations.
14+
type RAMResourceShareAPI interface {
15+
GetResourceShares(ctx context.Context, params *ram.GetResourceSharesInput, optFns ...func(*ram.Options)) (*ram.GetResourceSharesOutput, error)
16+
DeleteResourceShare(ctx context.Context, params *ram.DeleteResourceShareInput, optFns ...func(*ram.Options)) (*ram.DeleteResourceShareOutput, error)
17+
}
18+
19+
// NewResourceShares creates a new RAM Resource Share resource using the generic resource pattern.
20+
func NewResourceShares() AwsResource {
21+
return NewAwsResource(&resource.Resource[RAMResourceShareAPI]{
22+
ResourceTypeName: "resource-share",
23+
BatchSize: DefaultBatchSize,
24+
InitClient: WrapAwsInitClient(func(r *resource.Resource[RAMResourceShareAPI], cfg aws.Config) {
25+
r.Scope.Region = cfg.Region
26+
r.Client = ram.NewFromConfig(cfg)
27+
}),
28+
ConfigGetter: func(c config.Config) config.ResourceType {
29+
return c.ResourceShare
30+
},
31+
Lister: listResourceShares,
32+
Nuker: resource.SimpleBatchDeleter(deleteResourceShare),
33+
})
34+
}
35+
36+
// listResourceShares retrieves all RAM Resource Shares that match the config filters.
37+
func listResourceShares(ctx context.Context, client RAMResourceShareAPI, _ resource.Scope, cfg config.ResourceType) ([]*string, error) {
38+
var identifiers []*string
39+
paginator := ram.NewGetResourceSharesPaginator(client, &ram.GetResourceSharesInput{ResourceOwner: types.ResourceOwnerSelf})
40+
for paginator.HasMorePages() {
41+
page, err := paginator.NextPage(ctx)
42+
if err != nil {
43+
return nil, err
44+
}
45+
46+
for _, resourceShare := range page.ResourceShares {
47+
if cfg.ShouldInclude(config.ResourceValue{
48+
Name: resourceShare.Name,
49+
Time: resourceShare.CreationTime,
50+
Tags: convertRAMTagsToMap(resourceShare.Tags),
51+
}) {
52+
identifiers = append(identifiers, resourceShare.ResourceShareArn)
53+
}
54+
}
55+
}
56+
57+
return identifiers, nil
58+
}
59+
60+
// deleteResourceShare deletes a single RAM Resource Share.
61+
func deleteResourceShare(ctx context.Context, client RAMResourceShareAPI, arn *string) error {
62+
_, err := client.DeleteResourceShare(ctx, &ram.DeleteResourceShareInput{
63+
ResourceShareArn: arn,
64+
})
65+
return err
66+
}
67+
68+
func convertRAMTagsToMap(tags []types.Tag) map[string]string {
69+
tagMap := make(map[string]string)
70+
for _, tag := range tags {
71+
if tag.Key == nil || tag.Value == nil {
72+
continue
73+
}
74+
tagMap[*tag.Key] = *tag.Value
75+
}
76+
77+
return tagMap
78+
}
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
package resources
2+
3+
import (
4+
"context"
5+
"errors"
6+
"regexp"
7+
"testing"
8+
"time"
9+
10+
"github.com/aws/aws-sdk-go-v2/aws"
11+
"github.com/aws/aws-sdk-go-v2/service/ram"
12+
"github.com/aws/aws-sdk-go-v2/service/ram/types"
13+
"github.com/gruntwork-io/cloud-nuke/config"
14+
"github.com/gruntwork-io/cloud-nuke/resource"
15+
"github.com/stretchr/testify/require"
16+
)
17+
18+
type mockRAMResourceShareClient struct {
19+
getOutputs []ram.GetResourceSharesOutput
20+
getErr error
21+
getCall int
22+
deleteErr error
23+
deletedARNs []string
24+
}
25+
26+
func (m *mockRAMResourceShareClient) GetResourceShares(_ context.Context, _ *ram.GetResourceSharesInput, _ ...func(*ram.Options)) (*ram.GetResourceSharesOutput, error) {
27+
if m.getErr != nil {
28+
return nil, m.getErr
29+
}
30+
31+
if len(m.getOutputs) == 0 {
32+
return &ram.GetResourceSharesOutput{}, nil
33+
}
34+
35+
if m.getCall >= len(m.getOutputs) {
36+
last := m.getOutputs[len(m.getOutputs)-1]
37+
return &last, nil
38+
}
39+
40+
output := m.getOutputs[m.getCall]
41+
m.getCall++
42+
return &output, nil
43+
}
44+
45+
func (m *mockRAMResourceShareClient) DeleteResourceShare(_ context.Context, params *ram.DeleteResourceShareInput, _ ...func(*ram.Options)) (*ram.DeleteResourceShareOutput, error) {
46+
m.deletedARNs = append(m.deletedARNs, aws.ToString(params.ResourceShareArn))
47+
return &ram.DeleteResourceShareOutput{}, m.deleteErr
48+
}
49+
50+
func TestResourceShareList(t *testing.T) {
51+
t.Parallel()
52+
53+
now := time.Now()
54+
oldTime := now.Add(-1 * time.Hour)
55+
newTime := now.Add(1 * time.Hour)
56+
57+
shares := []types.ResourceShare{
58+
{
59+
Name: aws.String("share-1"),
60+
ResourceShareArn: aws.String("arn:aws:ram:us-east-1:123456789012:resource-share/1"),
61+
CreationTime: aws.Time(oldTime),
62+
Tags: []types.Tag{
63+
{Key: aws.String("env"), Value: aws.String("prod")},
64+
},
65+
},
66+
{
67+
Name: aws.String("share-2"),
68+
ResourceShareArn: aws.String("arn:aws:ram:us-east-1:123456789012:resource-share/2"),
69+
CreationTime: aws.Time(newTime),
70+
Tags: []types.Tag{
71+
{Key: aws.String("env"), Value: aws.String("dev")},
72+
},
73+
},
74+
}
75+
76+
tests := map[string]struct {
77+
configObj config.ResourceType
78+
expected []string
79+
}{
80+
"emptyFilter": {
81+
configObj: config.ResourceType{},
82+
expected: []string{
83+
"arn:aws:ram:us-east-1:123456789012:resource-share/1",
84+
"arn:aws:ram:us-east-1:123456789012:resource-share/2",
85+
},
86+
},
87+
"nameExclusionFilter": {
88+
configObj: config.ResourceType{
89+
ExcludeRule: config.FilterRule{
90+
NamesRegExp: []config.Expression{{RE: *regexp.MustCompile("share-1")}},
91+
},
92+
},
93+
expected: []string{"arn:aws:ram:us-east-1:123456789012:resource-share/2"},
94+
},
95+
"tagInclusionFilter": {
96+
configObj: config.ResourceType{
97+
IncludeRule: config.FilterRule{
98+
Tags: map[string]config.Expression{"env": {RE: *regexp.MustCompile("prod")}},
99+
},
100+
},
101+
expected: []string{"arn:aws:ram:us-east-1:123456789012:resource-share/1"},
102+
},
103+
"timeAfterExclusionFilter": {
104+
configObj: config.ResourceType{
105+
ExcludeRule: config.FilterRule{
106+
TimeAfter: aws.Time(now),
107+
},
108+
},
109+
expected: []string{"arn:aws:ram:us-east-1:123456789012:resource-share/1"},
110+
},
111+
}
112+
113+
for name, tc := range tests {
114+
t.Run(name, func(t *testing.T) {
115+
t.Parallel()
116+
117+
mock := &mockRAMResourceShareClient{
118+
getOutputs: []ram.GetResourceSharesOutput{{ResourceShares: shares}},
119+
}
120+
121+
ids, err := listResourceShares(context.Background(), mock, resource.Scope{Region: "us-east-1"}, tc.configObj)
122+
require.NoError(t, err)
123+
require.Equal(t, tc.expected, aws.ToStringSlice(ids))
124+
})
125+
}
126+
}
127+
128+
func TestResourceShareListPagination(t *testing.T) {
129+
t.Parallel()
130+
131+
mock := &mockRAMResourceShareClient{
132+
getOutputs: []ram.GetResourceSharesOutput{
133+
{
134+
ResourceShares: []types.ResourceShare{
135+
{Name: aws.String("share-1"), ResourceShareArn: aws.String("arn:1")},
136+
},
137+
NextToken: aws.String("next-page"),
138+
},
139+
{
140+
ResourceShares: []types.ResourceShare{
141+
{Name: aws.String("share-2"), ResourceShareArn: aws.String("arn:2")},
142+
},
143+
},
144+
},
145+
}
146+
147+
ids, err := listResourceShares(context.Background(), mock, resource.Scope{Region: "us-east-1"}, config.ResourceType{})
148+
require.NoError(t, err)
149+
require.Equal(t, []string{"arn:1", "arn:2"}, aws.ToStringSlice(ids))
150+
}
151+
152+
func TestDeleteResourceShare(t *testing.T) {
153+
t.Parallel()
154+
155+
mock := &mockRAMResourceShareClient{}
156+
err := deleteResourceShare(context.Background(), mock, aws.String("arn:test"))
157+
require.NoError(t, err)
158+
require.Equal(t, []string{"arn:test"}, mock.deletedARNs)
159+
}
160+
161+
func TestDeleteResourceShareError(t *testing.T) {
162+
t.Parallel()
163+
164+
mock := &mockRAMResourceShareClient{deleteErr: errors.New("delete failed")}
165+
err := deleteResourceShare(context.Background(), mock, aws.String("arn:test"))
166+
require.Error(t, err)
167+
}
168+
169+
func TestConvertRAMTagsToMap(t *testing.T) {
170+
t.Parallel()
171+
172+
tagMap := convertRAMTagsToMap([]types.Tag{
173+
{Key: aws.String("k1"), Value: aws.String("v1")},
174+
{Key: nil, Value: aws.String("v2")},
175+
{Key: aws.String("k3"), Value: nil},
176+
})
177+
178+
require.Equal(t, map[string]string{"k1": "v1"}, tagMap)
179+
}

config/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ type Config struct {
106106
OpenSearchDomain ResourceType `yaml:"OpenSearchDomain"`
107107
Redshift ResourceType `yaml:"Redshift"`
108108
RedshiftSnapshotCopyGrant ResourceType `yaml:"RedshiftSnapshotCopyGrant"`
109+
ResourceShare ResourceType `yaml:"ResourceShare"`
109110
RDSSnapshot ResourceType `yaml:"RDSSnapshot"`
110111
RDSParameterGroup ResourceType `yaml:"RDSParameterGroup"`
111112
RDSProxy ResourceType `yaml:"RDSProxy"`
@@ -248,6 +249,7 @@ func (c *Config) allResourceTypes() []*ResourceType {
248249
&c.OpenSearchDomain,
249250
&c.Redshift,
250251
&c.RedshiftSnapshotCopyGrant,
252+
&c.ResourceShare,
251253
&c.RDSSnapshot,
252254
&c.RDSParameterGroup,
253255
&c.RDSProxy,

config/config_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ func emptyConfig() *Config {
9595
OIDCProvider: ResourceType{FilterRule{}, FilterRule{}, "", false},
9696
OpenSearchDomain: ResourceType{FilterRule{}, FilterRule{}, "", false},
9797
Redshift: ResourceType{FilterRule{}, FilterRule{}, "", false},
98+
ResourceShare: ResourceType{FilterRule{}, FilterRule{}, "", false},
9899
RDSSnapshot: ResourceType{FilterRule{}, FilterRule{}, "", false},
99100
RDSParameterGroup: ResourceType{FilterRule{}, FilterRule{}, "", false},
100101
RDSProxy: ResourceType{FilterRule{}, FilterRule{}, "", false},

config/examples/complete.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1333,6 +1333,24 @@ Redshift:
13331333
test: "true"
13341334
timeout: 1h
13351335
protect_until_expire: true
1336+
ResourceShare:
1337+
include:
1338+
names_regex:
1339+
- that.*
1340+
- thats.*
1341+
time_after: 2024-02-02T00:00:00Z
1342+
time_before: 2024-01-01T00:00:00Z
1343+
tags:
1344+
test: "true"
1345+
exclude:
1346+
names_regex:
1347+
- this.*
1348+
time_after: 2024-02-02T00:00:00Z
1349+
time_before: 2024-01-01T00:00:00Z
1350+
tags:
1351+
test: "true"
1352+
timeout: 1h
1353+
protect_until_expire: true
13361354
RDSSnapshot:
13371355
include:
13381356
names_regex:

docs/supported-resources.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ cloud-nuke supports inspecting and deleting the following AWS resources. The **C
101101
| `rds-subnet-group` | RDS Subnet Group |
102102
| `redshift` | Redshift Cluster |
103103
| `redshift-snapshot-copy-grant` | Redshift Snapshot Copy Grant |
104+
| `resource-share` | RAM Resource Share |
104105
| `route-table` | Route Table |
105106
| `route53-cidr-collection` | Route53 CIDR Collection |
106107
| `route53-hosted-zone` | Route53 Hosted Zone |
@@ -240,6 +241,7 @@ This table shows which filtering features are supported for each resource type i
240241
| rds-subnet-group | DBSubnetGroups || |||
241242
| redshift | Redshift ||| ||
242243
| redshift-snapshot-copy-grant | RedshiftSnapshotCopyGrant || | ||
244+
| resource-share | ResourceShare |||||
243245
| route-table | RouteTable |||||
244246
| route53-cidr-collection | Route53CIDRCollection || | | |
245247
| route53-hosted-zone | Route53HostedZone || || |

go.mod

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ require (
77
cloud.google.com/go/functions v1.19.7
88
cloud.google.com/go/pubsub v1.49.0
99
cloud.google.com/go/storage v1.50.0
10-
github.com/aws/aws-sdk-go-v2 v1.41.3
10+
github.com/aws/aws-sdk-go-v2 v1.41.4
1111
github.com/aws/aws-sdk-go-v2/config v1.29.5
1212
github.com/aws/aws-sdk-go-v2/service/accessanalyzer v1.36.12
1313
github.com/aws/aws-sdk-go-v2/service/acm v1.30.17
@@ -97,15 +97,16 @@ require (
9797
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.8 // indirect
9898
github.com/aws/aws-sdk-go-v2/credentials v1.17.58 // indirect
9999
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27 // indirect
100-
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.19 // indirect
101-
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19 // indirect
100+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 // indirect
101+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20 // indirect
102102
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 // indirect
103103
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.31 // indirect
104104
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 // indirect
105105
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.5.5 // indirect
106106
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.12 // indirect
107107
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.12 // indirect
108108
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.12 // indirect
109+
github.com/aws/aws-sdk-go-v2/service/ram v1.36.2 // indirect
109110
github.com/aws/aws-sdk-go-v2/service/sso v1.24.14 // indirect
110111
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13 // indirect
111112
github.com/cespare/xxhash/v2 v2.3.0 // indirect

go.sum

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYew
4949
github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk=
5050
github.com/aws/aws-sdk-go-v2 v1.41.3 h1:4kQ/fa22KjDt13QCy1+bYADvdgcxpfH18f0zP542kZA=
5151
github.com/aws/aws-sdk-go-v2 v1.41.3/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
52+
github.com/aws/aws-sdk-go-v2 v1.41.4 h1:10f50G7WyU02T56ox1wWXq+zTX9I1zxG46HYuG1hH/k=
53+
github.com/aws/aws-sdk-go-v2 v1.41.4/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
5254
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.8 h1:zAxi9p3wsZMIaVCdoiQp2uZ9k1LsZvmAnoTBeZPXom0=
5355
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.8/go.mod h1:3XkePX5dSaxveLAYY7nsbsZZrKxCyEuE5pM4ziFxyGg=
5456
github.com/aws/aws-sdk-go-v2/config v1.29.5 h1:4lS2IB+wwkj5J43Tq/AwvnscBerBJtQQ6YS7puzCI1k=
@@ -59,8 +61,12 @@ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27 h1:7lOW8NUwE9UZekS1DYoiPd
5961
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27/go.mod h1:w1BASFIPOPUae7AgaH4SbjNbfdkxuggLyGfNFTn8ITY=
6062
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.19 h1:/sECfyq2JTifMI2JPyZ4bdRN77zJmr6SrS1eL3augIA=
6163
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.19/go.mod h1:dMf8A5oAqr9/oxOfLkC/c2LU/uMcALP0Rgn2BD5LWn0=
64+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 h1:CNXO7mvgThFGqOFgbNAP2nol2qAWBOGfqR/7tQlvLmc=
65+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20/go.mod h1:oydPDJKcfMhgfcgBUZaG+toBbwy8yPWubJXBVERtI4o=
6266
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19 h1:AWeJMk33GTBf6J20XJe6qZoRSJo0WfUhsMdUKhoODXE=
6367
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19/go.mod h1:+GWrYoaAsV7/4pNHpwh1kiNLXkKaSoppxQq9lbH8Ejw=
68+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20 h1:tN6W/hg+pkM+tf9XDkWUbDEjGLb+raoBMFsTodcoYKw=
69+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20/go.mod h1:YJ898MhD067hSHA6xYCx5ts/jEd8BSOLtQDL3iZsvbc=
6470
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 h1:Pg9URiobXy85kgFev3og2CuOZ8JZUBENF+dcgWBaYNk=
6571
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
6672
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.31 h1:8IwBjuLdqIO1dGB+dZ9zJEl8wzY3bVYxcs0Xyu/Lsc0=
@@ -155,6 +161,8 @@ github.com/aws/aws-sdk-go-v2/service/networkfirewall v1.44.13 h1:fn05X6eXYxQwZcz
155161
github.com/aws/aws-sdk-go-v2/service/networkfirewall v1.44.13/go.mod h1:RyXD4m4OOrMULbAgMDjEI7nMYIxCEb+KJ+zBB5ak3Og=
156162
github.com/aws/aws-sdk-go-v2/service/opensearch v1.45.10 h1:v0VALMz6htCysb4yHVl97EUO1MOhuZZEDz+Fq2lnce0=
157163
github.com/aws/aws-sdk-go-v2/service/opensearch v1.45.10/go.mod h1:wNaZJ8cVFw8W0kjhatPIcGNFkHNN2hZrAU7SZFldZM0=
164+
github.com/aws/aws-sdk-go-v2/service/ram v1.36.2 h1:OTuj3yT5iLl37At9ZVUNn+JkD2yZLrZ3MvN9k46CxH4=
165+
github.com/aws/aws-sdk-go-v2/service/ram v1.36.2/go.mod h1:lrQ7t9FfRKuxQxfJx1PUDnaoSyiq+wGcHATTeW18s34=
158166
github.com/aws/aws-sdk-go-v2/service/rds v1.93.11 h1:ibWYH+Bc59bDU9YG82HdNIP7MDlFNmlDZp94wKtkUyE=
159167
github.com/aws/aws-sdk-go-v2/service/rds v1.93.11/go.mod h1:LegGb8OqR5/uLP0RUEqoK2de8FNzDXIejcxpTiGSGVk=
160168
github.com/aws/aws-sdk-go-v2/service/redshift v1.53.11 h1:3JCZYhdJXv2HPctK3MlaHR4oGWU0/ihN5i3BxAxi110=

0 commit comments

Comments
 (0)