Skip to content

Commit 3495fcc

Browse files
committed
feat(cloudcontrol): implement rate limit w/ better logging
1 parent 5f9ab5f commit 3495fcc

File tree

1 file changed

+33
-15
lines changed

1 file changed

+33
-15
lines changed

resources/cloudcontrol.go

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"time"
78

89
"github.com/google/uuid"
10+
"github.com/gotidy/ptr"
911
"github.com/pkg/errors"
1012
"github.com/sirupsen/logrus"
13+
"go.uber.org/ratelimit"
1114

12-
"github.com/aws/aws-sdk-go/aws"
1315
"github.com/aws/aws-sdk-go/aws/awserr"
1416
"github.com/aws/aws-sdk-go/service/cloudcontrolapi"
1517

@@ -53,6 +55,15 @@ func init() {
5355
RegisterCloudControl("AWS::NetworkFirewall::RuleGroup")
5456
}
5557

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.
5667
func RegisterCloudControl(typeName string) {
5768
registry.Register(&registry.Registration{
5869
Name: typeName,
@@ -66,26 +77,31 @@ func RegisterCloudControl(typeName string) {
6677

6778
type CloudControlResourceLister struct {
6879
TypeName string
80+
81+
logger *logrus.Entry
6982
}
7083

7184
func (l *CloudControlResourceLister) List(_ context.Context, o interface{}) ([]resource.Resource, error) {
7285
opts := o.(*nuke.ListerOpts)
86+
l.logger = opts.Logger.WithField("type-name", l.TypeName)
7387

7488
svc := cloudcontrolapi.New(opts.Session)
89+
resources := make([]resource.Resource, 0)
7590

7691
params := &cloudcontrolapi.ListResourcesInput{
77-
TypeName: aws.String(l.TypeName),
92+
TypeName: ptr.String(l.TypeName),
93+
MaxResults: ptr.Int64(1),
7894
}
79-
resources := make([]resource.Resource, 0)
95+
8096
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()
8398

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))
85102
if err != nil {
86-
logrus.
103+
l.logger.
87104
WithError(errors.WithStack(err)).
88-
WithField("type-name", l.TypeName).
89105
WithField("identifier", identifier).
90106
Error("failed to parse cloud control properties")
91107
continue
@@ -117,17 +133,19 @@ func (l *CloudControlResourceLister) List(_ context.Context, o interface{}) ([]r
117133
return resources, nil
118134
}
119135

120-
func cloudControlParseProperties(payload string) (types.Properties, error) {
136+
func (l *CloudControlResourceLister) cloudControlParseProperties(payload string) (types.Properties, error) {
121137
// Warning: The implementation of this function is not very straightforward,
122138
// because the aws-nuke filter functions expect a very rigid structure and
123139
// the properties from the Cloud Control API are very dynamic.
124140

141+
properties := types.NewProperties()
125142
propMap := map[string]interface{}{}
143+
126144
err := json.Unmarshal([]byte(payload), &propMap)
127145
if err != nil {
128-
return nil, err
146+
return properties, err
129147
}
130-
properties := types.NewProperties()
148+
131149
for name, value := range propMap {
132150
switch v := value.(type) {
133151
case string:
@@ -147,12 +165,12 @@ func cloudControlParseProperties(payload string) (types.Properties, error) {
147165
v2["Value"],
148166
)
149167
} else {
150-
logrus.
168+
l.logger.
151169
WithField("value", fmt.Sprintf("%q", v)).
152170
Debugf("nested cloud control property type []%T is not supported", value)
153171
}
154172
default:
155-
logrus.
173+
l.logger.
156174
WithField("value", fmt.Sprintf("%q", v)).
157175
Debugf("nested cloud control property type []%T is not supported", value)
158176
}
@@ -163,9 +181,9 @@ func cloudControlParseProperties(payload string) (types.Properties, error) {
163181
// properties.Set, because it would fall back to
164182
// fmt.Sprintf. Since the cloud control properties are
165183
// nested it would create properties that are not
166-
// suitable for filtering. Therefore we have to
184+
// suitable for filtering. Therefore, we have to
167185
// implemented more sophisticated parsing.
168-
logrus.
186+
l.logger.
169187
WithField("value", fmt.Sprintf("%q", v)).
170188
Debugf("cloud control property type %T is not supported", v)
171189
}

0 commit comments

Comments
 (0)