Skip to content

Commit 450392d

Browse files
ApiGateway: bugfix to restore FilterFunc for correct mapping of resources (#1332)
1 parent a4ba914 commit 450392d

14 files changed

+461
-0
lines changed

Diff for: README.md

+6
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ As a quick start, the following IAM policy can be used to grant the all permissi
143143
"cloudwatch:GetMetricData",
144144
"cloudwatch:GetMetricStatistics",
145145
"cloudwatch:ListMetrics",
146+
"apigateway:GET",
146147
"aps:ListWorkspaces",
147148
"autoscaling:DescribeAutoScalingGroups",
148149
"dms:DescribeReplicationInstances",
@@ -170,6 +171,11 @@ These are the bare minimum permissions required to run Static and Discovery Jobs
170171
"cloudwatch:ListMetrics"
171172
```
172173

174+
This permission is required to discover resources for the AWS/ApiGateway namespace
175+
```json
176+
"apigateway:GET"
177+
```
178+
173179
This permission is required to discover resources for the AWS/AutoScaling namespace
174180
```json
175181
"autoscaling:DescribeAutoScalingGroups"

Diff for: go.mod

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ require (
88
github.com/aws/aws-sdk-go-v2/config v1.27.4
99
github.com/aws/aws-sdk-go-v2/credentials v1.17.4
1010
github.com/aws/aws-sdk-go-v2/service/amp v1.25.1
11+
github.com/aws/aws-sdk-go-v2/service/apigateway v1.23.3
12+
github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.20.1
1113
github.com/aws/aws-sdk-go-v2/service/autoscaling v1.40.2
1214
github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.36.1
1315
github.com/aws/aws-sdk-go-v2/service/databasemigrationservice v1.38.1

Diff for: go.sum

+4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7
1616
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
1717
github.com/aws/aws-sdk-go-v2/service/amp v1.25.1 h1:kdytWcOvNUmqSVWSRNeAirv4e1j397UQC3d6ofS2CxQ=
1818
github.com/aws/aws-sdk-go-v2/service/amp v1.25.1/go.mod h1:cG/8NqEsOGrLYBgIeixa7aaHzY8RvbUhMQYJBbSdD+c=
19+
github.com/aws/aws-sdk-go-v2/service/apigateway v1.23.3 h1:STt7Loc3FYm8xoU0zF8cvXLgk0aXFEhBpw/QWoxey+8=
20+
github.com/aws/aws-sdk-go-v2/service/apigateway v1.23.3/go.mod h1:Cz7oE8VP37eZ/JYhWoeRaqX2GoecJ2EkO7OEcjV4KQ4=
21+
github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.20.1 h1:nOJwQpU2wDe2qtw+vwkywJ44UhK66P6+1UIRotE7Jmo=
22+
github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.20.1/go.mod h1:A95FM8hxO6umoiROudoYtTmZYl7KN9nbez8deLDOCnA=
1923
github.com/aws/aws-sdk-go-v2/service/autoscaling v1.40.2 h1:nbDDSiI4JpmOzSvtxa1jduRMmib+t/IU2mWgF3mSSBU=
2024
github.com/aws/aws-sdk-go-v2/service/autoscaling v1.40.2/go.mod h1:g+K5WaPD3zegO3keZ8kV4dzPzU80aypAXSCXy7WL5xo=
2125
github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.36.1 h1:mQySuI87thHtcbZvEDjwUROGWikU6fqgpHklCBXpJU4=

Diff for: pkg/clients/tagging/v1/client.go

+8
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"fmt"
66

77
"github.com/aws/aws-sdk-go/aws"
8+
"github.com/aws/aws-sdk-go/service/apigateway/apigatewayiface"
9+
"github.com/aws/aws-sdk-go/service/apigatewayv2/apigatewayv2iface"
810
"github.com/aws/aws-sdk-go/service/autoscaling/autoscalingiface"
911
"github.com/aws/aws-sdk-go/service/databasemigrationservice/databasemigrationserviceiface"
1012
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
@@ -25,6 +27,8 @@ type client struct {
2527
logger logging.Logger
2628
taggingAPI resourcegroupstaggingapiiface.ResourceGroupsTaggingAPIAPI
2729
autoscalingAPI autoscalingiface.AutoScalingAPI
30+
apiGatewayAPI apigatewayiface.APIGatewayAPI
31+
apiGatewayV2API apigatewayv2iface.ApiGatewayV2API
2832
ec2API ec2iface.EC2API
2933
dmsAPI databasemigrationserviceiface.DatabaseMigrationServiceAPI
3034
prometheusSvcAPI prometheusserviceiface.PrometheusServiceAPI
@@ -36,6 +40,8 @@ func NewClient(
3640
logger logging.Logger,
3741
taggingAPI resourcegroupstaggingapiiface.ResourceGroupsTaggingAPIAPI,
3842
autoscalingAPI autoscalingiface.AutoScalingAPI,
43+
apiGatewayAPI apigatewayiface.APIGatewayAPI,
44+
apiGatewayV2API apigatewayv2iface.ApiGatewayV2API,
3945
ec2API ec2iface.EC2API,
4046
dmsClient databasemigrationserviceiface.DatabaseMigrationServiceAPI,
4147
prometheusClient prometheusserviceiface.PrometheusServiceAPI,
@@ -46,6 +52,8 @@ func NewClient(
4652
logger: logger,
4753
taggingAPI: taggingAPI,
4854
autoscalingAPI: autoscalingAPI,
55+
apiGatewayAPI: apiGatewayAPI,
56+
apiGatewayV2API: apiGatewayV2API,
4957
ec2API: ec2API,
5058
dmsAPI: dmsClient,
5159
prometheusSvcAPI: prometheusClient,

Diff for: pkg/clients/tagging/v1/filters.go

+56
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ package v1
33
import (
44
"context"
55
"fmt"
6+
"strings"
67

78
"github.com/aws/aws-sdk-go/aws"
89
"github.com/aws/aws-sdk-go/aws/arn"
10+
"github.com/aws/aws-sdk-go/service/apigateway"
11+
"github.com/aws/aws-sdk-go/service/apigatewayv2"
912
"github.com/aws/aws-sdk-go/service/autoscaling"
1013
"github.com/aws/aws-sdk-go/service/databasemigrationservice"
1114
"github.com/aws/aws-sdk-go/service/ec2"
@@ -27,6 +30,59 @@ type ServiceFilter struct {
2730

2831
// ServiceFilters maps a service namespace to (optional) ServiceFilter
2932
var ServiceFilters = map[string]ServiceFilter{
33+
"AWS/ApiGateway": {
34+
// ApiGateway ARNs use the Id (for v1 REST APIs) and ApiId (for v2 APIs) instead of
35+
// the ApiName (display name). See https://docs.aws.amazon.com/apigateway/latest/developerguide/arn-format-reference.html
36+
// However, in metrics, the ApiId dimension uses the ApiName as value.
37+
//
38+
// Here we use the ApiGateway API to map resource correctly. For backward compatibility,
39+
// in v1 REST APIs we change the ARN to replace the ApiId with ApiName, while for v2 APIs
40+
// we leave the ARN as-is.
41+
FilterFunc: func(ctx context.Context, client client, inputResources []*model.TaggedResource) ([]*model.TaggedResource, error) {
42+
var limit int64 = 500 // max number of results per page. default=25, max=500
43+
const maxPages = 10
44+
input := apigateway.GetRestApisInput{Limit: &limit}
45+
output := apigateway.GetRestApisOutput{}
46+
var pageNum int
47+
48+
err := client.apiGatewayAPI.GetRestApisPagesWithContext(ctx, &input, func(page *apigateway.GetRestApisOutput, _ bool) bool {
49+
promutil.APIGatewayAPICounter.Inc()
50+
pageNum++
51+
output.Items = append(output.Items, page.Items...)
52+
return pageNum <= maxPages
53+
})
54+
if err != nil {
55+
return nil, fmt.Errorf("error calling apiGatewayAPI.GetRestApisPages, %w", err)
56+
}
57+
58+
outputV2, err := client.apiGatewayV2API.GetApisWithContext(ctx, &apigatewayv2.GetApisInput{})
59+
promutil.APIGatewayAPIV2Counter.Inc()
60+
if err != nil {
61+
return nil, fmt.Errorf("error calling apiGatewayAPIv2.GetApis, %w", err)
62+
}
63+
64+
var outputResources []*model.TaggedResource
65+
for _, resource := range inputResources {
66+
for i, gw := range output.Items {
67+
if strings.HasSuffix(resource.ARN, "/restapis/"+*gw.Id) {
68+
r := resource
69+
r.ARN = strings.ReplaceAll(resource.ARN, *gw.Id, *gw.Name)
70+
outputResources = append(outputResources, r)
71+
output.Items = append(output.Items[:i], output.Items[i+1:]...)
72+
break
73+
}
74+
}
75+
for i, gw := range outputV2.Items {
76+
if strings.HasSuffix(resource.ARN, "/apis/"+*gw.ApiId) {
77+
outputResources = append(outputResources, resource)
78+
outputV2.Items = append(outputV2.Items[:i], outputV2.Items[i+1:]...)
79+
break
80+
}
81+
}
82+
}
83+
return outputResources, nil
84+
},
85+
},
3086
"AWS/AutoScaling": {
3187
ResourceFunc: func(ctx context.Context, client client, job model.DiscoveryJob, region string) ([]*model.TaggedResource, error) {
3288
pageNum := 0

Diff for: pkg/clients/tagging/v1/filters_test.go

+191
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ import (
77

88
"github.com/aws/aws-sdk-go/aws"
99
"github.com/aws/aws-sdk-go/aws/request"
10+
"github.com/aws/aws-sdk-go/service/apigateway"
11+
"github.com/aws/aws-sdk-go/service/apigateway/apigatewayiface"
12+
"github.com/aws/aws-sdk-go/service/apigatewayv2"
13+
"github.com/aws/aws-sdk-go/service/apigatewayv2/apigatewayv2iface"
1014
"github.com/aws/aws-sdk-go/service/databasemigrationservice"
1115
"github.com/aws/aws-sdk-go/service/databasemigrationservice/databasemigrationserviceiface"
1216

@@ -28,6 +32,174 @@ func TestValidServiceNames(t *testing.T) {
2832
}
2933
}
3034

35+
func TestApiGatewayFilterFunc(t *testing.T) {
36+
tests := []struct {
37+
name string
38+
iface client
39+
inputResources []*model.TaggedResource
40+
outputResources []*model.TaggedResource
41+
}{
42+
{
43+
"api gateway resources skip stages",
44+
client{
45+
apiGatewayAPI: apiGatewayClient{
46+
getRestApisOutput: &apigateway.GetRestApisOutput{
47+
Items: []*apigateway.RestApi{
48+
{
49+
ApiKeySource: nil,
50+
BinaryMediaTypes: nil,
51+
CreatedDate: nil,
52+
Description: nil,
53+
DisableExecuteApiEndpoint: nil,
54+
EndpointConfiguration: nil,
55+
Id: aws.String("gwid1234"),
56+
MinimumCompressionSize: nil,
57+
Name: aws.String("apiname"),
58+
Policy: nil,
59+
Tags: nil,
60+
Version: nil,
61+
Warnings: nil,
62+
},
63+
},
64+
Position: nil,
65+
},
66+
},
67+
apiGatewayV2API: apiGatewayV2Client{
68+
getRestApisOutput: &apigatewayv2.GetApisOutput{
69+
Items: []*apigatewayv2.Api{},
70+
},
71+
},
72+
},
73+
[]*model.TaggedResource{
74+
{
75+
ARN: "arn:aws:apigateway:us-east-1::/restapis/gwid1234/stages/main",
76+
Namespace: "apigateway",
77+
Region: "us-east-1",
78+
Tags: []model.Tag{
79+
{
80+
Key: "Test",
81+
Value: "Value",
82+
},
83+
},
84+
},
85+
{
86+
ARN: "arn:aws:apigateway:us-east-1::/restapis/gwid1234",
87+
Namespace: "apigateway",
88+
Region: "us-east-1",
89+
Tags: []model.Tag{
90+
{
91+
Key: "Test",
92+
Value: "Value 2",
93+
},
94+
},
95+
},
96+
},
97+
[]*model.TaggedResource{
98+
{
99+
ARN: "arn:aws:apigateway:us-east-1::/restapis/apiname",
100+
Namespace: "apigateway",
101+
Region: "us-east-1",
102+
Tags: []model.Tag{
103+
{
104+
Key: "Test",
105+
Value: "Value 2",
106+
},
107+
},
108+
},
109+
},
110+
},
111+
{
112+
"api gateway v2",
113+
client{
114+
apiGatewayAPI: apiGatewayClient{
115+
getRestApisOutput: &apigateway.GetRestApisOutput{
116+
Items: []*apigateway.RestApi{},
117+
},
118+
},
119+
apiGatewayV2API: apiGatewayV2Client{
120+
getRestApisOutput: &apigatewayv2.GetApisOutput{
121+
Items: []*apigatewayv2.Api{
122+
{
123+
CreatedDate: nil,
124+
Description: nil,
125+
DisableExecuteApiEndpoint: nil,
126+
ApiId: aws.String("gwid9876"),
127+
Name: aws.String("apiv2name"),
128+
Tags: nil,
129+
Version: nil,
130+
Warnings: nil,
131+
},
132+
},
133+
NextToken: nil,
134+
},
135+
},
136+
},
137+
[]*model.TaggedResource{
138+
{
139+
ARN: "arn:aws:apigateway:us-east-1::/apis/gwid9876/stages/$default",
140+
Namespace: "apigateway",
141+
Region: "us-east-1",
142+
Tags: []model.Tag{
143+
{
144+
Key: "Test",
145+
Value: "Value",
146+
},
147+
},
148+
},
149+
{
150+
ARN: "arn:aws:apigateway:us-east-1::/apis/gwid9876",
151+
Namespace: "apigateway",
152+
Region: "us-east-1",
153+
Tags: []model.Tag{
154+
{
155+
Key: "Test",
156+
Value: "Value 2",
157+
},
158+
},
159+
},
160+
},
161+
[]*model.TaggedResource{
162+
{
163+
ARN: "arn:aws:apigateway:us-east-1::/apis/gwid9876",
164+
Namespace: "apigateway",
165+
Region: "us-east-1",
166+
Tags: []model.Tag{
167+
{
168+
Key: "Test",
169+
Value: "Value 2",
170+
},
171+
},
172+
},
173+
},
174+
},
175+
}
176+
177+
for _, test := range tests {
178+
t.Run(test.name, func(t *testing.T) {
179+
apigateway := ServiceFilters["AWS/ApiGateway"]
180+
181+
outputResources, err := apigateway.FilterFunc(context.Background(), test.iface, test.inputResources)
182+
if err != nil {
183+
t.Logf("Error from FilterFunc: %v", err)
184+
t.FailNow()
185+
}
186+
if len(outputResources) != len(test.outputResources) {
187+
t.Logf("len(outputResources) = %d, want %d", len(outputResources), len(test.outputResources))
188+
t.Fail()
189+
}
190+
for i, resource := range outputResources {
191+
if len(test.outputResources) <= i {
192+
break
193+
}
194+
wantResource := *test.outputResources[i]
195+
if !reflect.DeepEqual(*resource, wantResource) {
196+
t.Errorf("outputResources[%d] = %+v, want %+v", i, *resource, wantResource)
197+
}
198+
}
199+
})
200+
}
201+
}
202+
31203
func TestDMSFilterFunc(t *testing.T) {
32204
tests := []struct {
33205
name string
@@ -243,6 +415,25 @@ func TestDMSFilterFunc(t *testing.T) {
243415
}
244416
}
245417

418+
type apiGatewayClient struct {
419+
apigatewayiface.APIGatewayAPI
420+
getRestApisOutput *apigateway.GetRestApisOutput
421+
}
422+
423+
func (apigateway apiGatewayClient) GetRestApisPagesWithContext(_ aws.Context, _ *apigateway.GetRestApisInput, fn func(*apigateway.GetRestApisOutput, bool) bool, _ ...request.Option) error {
424+
fn(apigateway.getRestApisOutput, true)
425+
return nil
426+
}
427+
428+
type apiGatewayV2Client struct {
429+
apigatewayv2iface.ApiGatewayV2API
430+
getRestApisOutput *apigatewayv2.GetApisOutput
431+
}
432+
433+
func (apigateway apiGatewayV2Client) GetApisWithContext(_ aws.Context, _ *apigatewayv2.GetApisInput, _ ...request.Option) (*apigatewayv2.GetApisOutput, error) {
434+
return apigateway.getRestApisOutput, nil
435+
}
436+
246437
type dmsClient struct {
247438
databasemigrationserviceiface.DatabaseMigrationServiceAPI
248439
describeReplicationInstancesOutput *databasemigrationservice.DescribeReplicationInstancesOutput

Diff for: pkg/clients/tagging/v2/client.go

+8
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66

77
"github.com/aws/aws-sdk-go-v2/aws"
88
"github.com/aws/aws-sdk-go-v2/service/amp"
9+
"github.com/aws/aws-sdk-go-v2/service/apigateway"
10+
"github.com/aws/aws-sdk-go-v2/service/apigatewayv2"
911
"github.com/aws/aws-sdk-go-v2/service/autoscaling"
1012
"github.com/aws/aws-sdk-go-v2/service/databasemigrationservice"
1113
"github.com/aws/aws-sdk-go-v2/service/ec2"
@@ -25,6 +27,8 @@ type client struct {
2527
logger logging.Logger
2628
taggingAPI *resourcegroupstaggingapi.Client
2729
autoscalingAPI *autoscaling.Client
30+
apiGatewayAPI *apigateway.Client
31+
apiGatewayV2API *apigatewayv2.Client
2832
ec2API *ec2.Client
2933
dmsAPI *databasemigrationservice.Client
3034
prometheusSvcAPI *amp.Client
@@ -36,6 +40,8 @@ func NewClient(
3640
logger logging.Logger,
3741
taggingAPI *resourcegroupstaggingapi.Client,
3842
autoscalingAPI *autoscaling.Client,
43+
apiGatewayAPI *apigateway.Client,
44+
apiGatewayV2API *apigatewayv2.Client,
3945
ec2API *ec2.Client,
4046
dmsClient *databasemigrationservice.Client,
4147
prometheusClient *amp.Client,
@@ -46,6 +52,8 @@ func NewClient(
4652
logger: logger,
4753
taggingAPI: taggingAPI,
4854
autoscalingAPI: autoscalingAPI,
55+
apiGatewayAPI: apiGatewayAPI,
56+
apiGatewayV2API: apiGatewayV2API,
4957
ec2API: ec2API,
5058
dmsAPI: dmsClient,
5159
prometheusSvcAPI: prometheusClient,

0 commit comments

Comments
 (0)