@@ -4,12 +4,14 @@ import (
4
4
"context"
5
5
"encoding/json"
6
6
"fmt"
7
+ "time"
7
8
8
9
"github.com/google/uuid"
10
+ "github.com/gotidy/ptr"
9
11
"github.com/pkg/errors"
10
12
"github.com/sirupsen/logrus"
13
+ "go.uber.org/ratelimit"
11
14
12
- "github.com/aws/aws-sdk-go/aws"
13
15
"github.com/aws/aws-sdk-go/aws/awserr"
14
16
"github.com/aws/aws-sdk-go/service/cloudcontrolapi"
15
17
@@ -53,6 +55,15 @@ func init() {
53
55
RegisterCloudControl ("AWS::NetworkFirewall::RuleGroup" )
54
56
}
55
57
58
+ // describeRateLimit is a rate limiter to avoid throttling when describing resources via the cloud control api.
59
+ // AWS does not publish the rate limits for the cloud control api. Testing shows it fails around 30-35 requests per
60
+ // second, therefore we set the rate limit to 25 requests per second to try and stay under the limit at all times.
61
+ var describeRateLimit = ratelimit .New (25 , ratelimit .Per (time .Second ))
62
+
63
+ // RegisterCloudControl registers a resource type for the Cloud Control API. This is a unique function that is used
64
+ // in two different places. The first place is in the init() function of this file, where it is used to register
65
+ // a select subset of Cloud Control API resource types. The second place is in nuke command file, where it is used
66
+ // to dynamically register any resource type provided via the `--cloud-control` flag.
56
67
func RegisterCloudControl (typeName string ) {
57
68
registry .Register (& registry.Registration {
58
69
Name : typeName ,
@@ -66,26 +77,31 @@ func RegisterCloudControl(typeName string) {
66
77
67
78
type CloudControlResourceLister struct {
68
79
TypeName string
80
+
81
+ logger * logrus.Entry
69
82
}
70
83
71
84
func (l * CloudControlResourceLister ) List (_ context.Context , o interface {}) ([]resource.Resource , error ) {
72
85
opts := o .(* nuke.ListerOpts )
86
+ l .logger = opts .Logger .WithField ("type-name" , l .TypeName )
73
87
74
88
svc := cloudcontrolapi .New (opts .Session )
89
+ resources := make ([]resource.Resource , 0 )
75
90
76
91
params := & cloudcontrolapi.ListResourcesInput {
77
- TypeName : aws .String (l .TypeName ),
92
+ TypeName : ptr .String (l .TypeName ),
93
+ MaxResults : ptr .Int64 (1 ),
78
94
}
79
- resources := make ([]resource. Resource , 0 )
95
+
80
96
if err := svc .ListResourcesPages (params , func (page * cloudcontrolapi.ListResourcesOutput , lastPage bool ) bool {
81
- for _ , desc := range page .ResourceDescriptions {
82
- identifier := aws .StringValue (desc .Identifier )
97
+ describeRateLimit .Take ()
83
98
84
- properties , err := cloudControlParseProperties (aws .StringValue (desc .Properties ))
99
+ for _ , desc := range page .ResourceDescriptions {
100
+ identifier := ptr .ToString (desc .Identifier )
101
+ properties , err := l .cloudControlParseProperties (ptr .ToString (desc .Properties ))
85
102
if err != nil {
86
- logrus .
103
+ l . logger .
87
104
WithError (errors .WithStack (err )).
88
- WithField ("type-name" , l .TypeName ).
89
105
WithField ("identifier" , identifier ).
90
106
Error ("failed to parse cloud control properties" )
91
107
continue
@@ -117,17 +133,19 @@ func (l *CloudControlResourceLister) List(_ context.Context, o interface{}) ([]r
117
133
return resources , nil
118
134
}
119
135
120
- func cloudControlParseProperties (payload string ) (types.Properties , error ) {
136
+ func ( l * CloudControlResourceLister ) cloudControlParseProperties (payload string ) (types.Properties , error ) {
121
137
// Warning: The implementation of this function is not very straightforward,
122
138
// because the aws-nuke filter functions expect a very rigid structure and
123
139
// the properties from the Cloud Control API are very dynamic.
124
140
141
+ properties := types .NewProperties ()
125
142
propMap := map [string ]interface {}{}
143
+
126
144
err := json .Unmarshal ([]byte (payload ), & propMap )
127
145
if err != nil {
128
- return nil , err
146
+ return properties , err
129
147
}
130
- properties := types . NewProperties ()
148
+
131
149
for name , value := range propMap {
132
150
switch v := value .(type ) {
133
151
case string :
@@ -147,12 +165,12 @@ func cloudControlParseProperties(payload string) (types.Properties, error) {
147
165
v2 ["Value" ],
148
166
)
149
167
} else {
150
- logrus .
168
+ l . logger .
151
169
WithField ("value" , fmt .Sprintf ("%q" , v )).
152
170
Debugf ("nested cloud control property type []%T is not supported" , value )
153
171
}
154
172
default :
155
- logrus .
173
+ l . logger .
156
174
WithField ("value" , fmt .Sprintf ("%q" , v )).
157
175
Debugf ("nested cloud control property type []%T is not supported" , value )
158
176
}
@@ -163,9 +181,9 @@ func cloudControlParseProperties(payload string) (types.Properties, error) {
163
181
// properties.Set, because it would fall back to
164
182
// fmt.Sprintf. Since the cloud control properties are
165
183
// nested it would create properties that are not
166
- // suitable for filtering. Therefore we have to
184
+ // suitable for filtering. Therefore, we have to
167
185
// implemented more sophisticated parsing.
168
- logrus .
186
+ l . logger .
169
187
WithField ("value" , fmt .Sprintf ("%q" , v )).
170
188
Debugf ("cloud control property type %T is not supported" , v )
171
189
}
0 commit comments