Skip to content

Commit 8c0853c

Browse files
add apigateway method & method response adapters (#716)
* add apigateway method adapter * add apigateway method response adapter * Adding some more links between these so they can find each other * Add potential links * Fixed errors for tests --------- Co-authored-by: Dylan Ratcliffe <dylan@overmind.tech>
1 parent fecd301 commit 8c0853c

File tree

10 files changed

+674
-5
lines changed

10 files changed

+674
-5
lines changed
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package adapters
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/aws/aws-sdk-go-v2/service/apigateway"
9+
"github.com/overmindtech/aws-source/adapterhelpers"
10+
"github.com/overmindtech/sdp-go"
11+
)
12+
13+
func apiGatewayMethodResponseGetFunc(ctx context.Context, client apigatewayClient, scope string, input *apigateway.GetMethodResponseInput) (*sdp.Item, error) {
14+
if input == nil {
15+
return nil, &sdp.QueryError{
16+
ErrorType: sdp.QueryError_NOTFOUND,
17+
ErrorString: "query must be in the format of: the rest-api-id/resource-id/http-method/status-code",
18+
}
19+
}
20+
21+
output, err := client.GetMethodResponse(ctx, input)
22+
if err != nil {
23+
return nil, err
24+
}
25+
26+
attributes, err := adapterhelpers.ToAttributesWithExclude(output, "tags")
27+
if err != nil {
28+
return nil, err
29+
}
30+
31+
// We create a custom ID of {rest-api-id}/{resource-id}/{http-method}/{status-code} e.g.
32+
// rest-api-id/resource-id/GET/200
33+
methodResponseID := fmt.Sprintf(
34+
"%s/%s/%s/%s",
35+
*input.RestApiId,
36+
*input.ResourceId,
37+
*input.HttpMethod,
38+
*input.StatusCode,
39+
)
40+
err = attributes.Set("MethodResponseID", methodResponseID)
41+
if err != nil {
42+
return nil, err
43+
}
44+
45+
item := &sdp.Item{
46+
Type: "apigateway-method-response",
47+
UniqueAttribute: "MethodResponseID",
48+
Attributes: attributes,
49+
Scope: scope,
50+
}
51+
52+
item.LinkedItemQueries = append(item.LinkedItemQueries, &sdp.LinkedItemQuery{
53+
Query: &sdp.Query{
54+
Type: "apigateway-method",
55+
Method: sdp.QueryMethod_GET,
56+
Query: fmt.Sprintf("%s/%s/%s", *input.RestApiId, *input.ResourceId, *input.HttpMethod),
57+
Scope: scope,
58+
},
59+
BlastPropagation: &sdp.BlastPropagation{
60+
// They are tightly coupled
61+
In: true,
62+
Out: true,
63+
},
64+
})
65+
66+
return item, nil
67+
}
68+
69+
func NewAPIGatewayMethodResponseAdapter(client apigatewayClient, accountID string, region string) *adapterhelpers.AlwaysGetAdapter[*apigateway.GetMethodResponseInput, *apigateway.GetMethodResponseOutput, *apigateway.GetMethodResponseInput, *apigateway.GetMethodResponseOutput, apigatewayClient, *apigateway.Options] {
70+
return &adapterhelpers.AlwaysGetAdapter[*apigateway.GetMethodResponseInput, *apigateway.GetMethodResponseOutput, *apigateway.GetMethodResponseInput, *apigateway.GetMethodResponseOutput, apigatewayClient, *apigateway.Options]{
71+
ItemType: "apigateway-method-response",
72+
Client: client,
73+
AccountID: accountID,
74+
Region: region,
75+
AdapterMetadata: apiGatewayMethodResponseAdapterMetadata,
76+
GetFunc: apiGatewayMethodResponseGetFunc,
77+
GetInputMapper: func(scope, query string) *apigateway.GetMethodResponseInput {
78+
// We are using a custom id of {rest-api-id}/{resource-id}/{http-method}/{status-code} e.g.
79+
// rest-api-id/resource-id/GET/200
80+
f := strings.Split(query, "/")
81+
if len(f) != 4 {
82+
return nil
83+
}
84+
85+
return &apigateway.GetMethodResponseInput{
86+
RestApiId: &f[0],
87+
ResourceId: &f[1],
88+
HttpMethod: &f[2],
89+
StatusCode: &f[3],
90+
}
91+
},
92+
DisableList: true,
93+
}
94+
}
95+
96+
var apiGatewayMethodResponseAdapterMetadata = Metadata.Register(&sdp.AdapterMetadata{
97+
Type: "apigateway-method-response",
98+
DescriptiveName: "API Gateway Method Response",
99+
Category: sdp.AdapterCategory_ADAPTER_CATEGORY_NETWORK,
100+
SupportedQueryMethods: &sdp.AdapterSupportedQueryMethods{
101+
Get: true,
102+
GetDescription: "Get a Method Response by rest-api id, resource id, http-method, and status-code",
103+
Search: true,
104+
SearchDescription: "Search Method Responses by ARN",
105+
},
106+
})
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package adapters
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"testing"
7+
"time"
8+
9+
"github.com/aws/aws-sdk-go-v2/aws"
10+
"github.com/aws/aws-sdk-go-v2/service/apigateway"
11+
"github.com/overmindtech/aws-source/adapterhelpers"
12+
"github.com/overmindtech/sdp-go"
13+
)
14+
15+
func (m *mockAPIGatewayClient) GetMethodResponse(ctx context.Context, params *apigateway.GetMethodResponseInput, optFns ...func(*apigateway.Options)) (*apigateway.GetMethodResponseOutput, error) {
16+
return &apigateway.GetMethodResponseOutput{
17+
ResponseModels: map[string]string{
18+
"application/json": "Empty",
19+
},
20+
StatusCode: aws.String("200"),
21+
}, nil
22+
}
23+
24+
func TestApiGatewayMethodResponseGetFunc(t *testing.T) {
25+
ctx := context.Background()
26+
cli := mockAPIGatewayClient{}
27+
28+
input := &apigateway.GetMethodResponseInput{
29+
RestApiId: aws.String("rest-api-id"),
30+
ResourceId: aws.String("resource-id"),
31+
HttpMethod: aws.String("GET"),
32+
StatusCode: aws.String("200"),
33+
}
34+
35+
item, err := apiGatewayMethodResponseGetFunc(ctx, &cli, "scope", input)
36+
if err != nil {
37+
t.Fatalf("unexpected error: %v", err)
38+
}
39+
40+
if err = item.Validate(); err != nil {
41+
t.Fatal(err)
42+
}
43+
44+
methodID := fmt.Sprintf("%s/%s/%s", *input.RestApiId, *input.ResourceId, *input.HttpMethod)
45+
46+
tests := adapterhelpers.QueryTests{
47+
{
48+
ExpectedType: "apigateway-method",
49+
ExpectedMethod: sdp.QueryMethod_GET,
50+
ExpectedQuery: methodID,
51+
ExpectedScope: "scope",
52+
},
53+
}
54+
55+
tests.Execute(t, item)
56+
}
57+
58+
func TestNewAPIGatewayMethodResponseAdapter(t *testing.T) {
59+
config, account, region := adapterhelpers.GetAutoConfig(t)
60+
61+
client := apigateway.NewFromConfig(config)
62+
63+
adapter := NewAPIGatewayMethodResponseAdapter(client, account, region)
64+
65+
test := adapterhelpers.E2ETest{
66+
Adapter: adapter,
67+
Timeout: 10 * time.Second,
68+
SkipList: true,
69+
}
70+
71+
test.Run(t)
72+
}

adapters/apigateway-method.go

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
package adapters
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/aws/aws-sdk-go-v2/service/apigateway"
9+
"github.com/overmindtech/aws-source/adapterhelpers"
10+
"github.com/overmindtech/sdp-go"
11+
)
12+
13+
type apigatewayClient interface {
14+
GetMethod(ctx context.Context, params *apigateway.GetMethodInput, optFns ...func(*apigateway.Options)) (*apigateway.GetMethodOutput, error)
15+
GetMethodResponse(ctx context.Context, params *apigateway.GetMethodResponseInput, optFns ...func(*apigateway.Options)) (*apigateway.GetMethodResponseOutput, error)
16+
}
17+
18+
func apiGatewayMethodGetFunc(ctx context.Context, client apigatewayClient, scope string, input *apigateway.GetMethodInput) (*sdp.Item, error) {
19+
if input == nil {
20+
return nil, &sdp.QueryError{
21+
ErrorType: sdp.QueryError_NOTFOUND,
22+
ErrorString: "query must be in the format of: the rest-api-id/resource-id/http-method",
23+
}
24+
}
25+
26+
output, err := client.GetMethod(ctx, input)
27+
if err != nil {
28+
return nil, err
29+
}
30+
31+
attributes, err := adapterhelpers.ToAttributesWithExclude(output, "tags")
32+
if err != nil {
33+
return nil, err
34+
}
35+
36+
// We create a custom ID of {rest-api-id}/{resource-id}/{http-method} e.g.
37+
// rest-api-id/resource-id/GET
38+
methodID := fmt.Sprintf(
39+
"%s/%s/%s",
40+
*input.RestApiId,
41+
*input.ResourceId,
42+
*input.HttpMethod,
43+
)
44+
err = attributes.Set("MethodID", methodID)
45+
if err != nil {
46+
return nil, err
47+
}
48+
49+
item := &sdp.Item{
50+
Type: "apigateway-method",
51+
UniqueAttribute: "MethodID",
52+
Attributes: attributes,
53+
Scope: scope,
54+
}
55+
56+
if output.MethodIntegration != nil {
57+
item.LinkedItemQueries = append(item.LinkedItemQueries, &sdp.LinkedItemQuery{
58+
Query: &sdp.Query{
59+
Type: "apigateway-integration",
60+
Method: sdp.QueryMethod_GET,
61+
Query: methodID,
62+
Scope: scope,
63+
},
64+
BlastPropagation: &sdp.BlastPropagation{
65+
// They are tightly coupled
66+
In: true,
67+
Out: true,
68+
},
69+
})
70+
}
71+
72+
if output.AuthorizerId != nil {
73+
item.LinkedItemQueries = append(item.LinkedItemQueries, &sdp.LinkedItemQuery{
74+
Query: &sdp.Query{
75+
Type: "apigateway-authorizer",
76+
Method: sdp.QueryMethod_GET,
77+
Query: fmt.Sprintf("%s/%s", *input.RestApiId, *output.AuthorizerId),
78+
Scope: scope,
79+
},
80+
BlastPropagation: &sdp.BlastPropagation{
81+
// Deleting authorizer will affect the method
82+
In: true,
83+
// Deleting method won't affect the authorizer
84+
Out: false,
85+
},
86+
})
87+
}
88+
89+
if output.RequestValidatorId != nil {
90+
item.LinkedItemQueries = append(item.LinkedItemQueries, &sdp.LinkedItemQuery{
91+
Query: &sdp.Query{
92+
Type: "apigateway-request-validator",
93+
Method: sdp.QueryMethod_GET,
94+
Query: fmt.Sprintf("%s/%s", *input.RestApiId, *output.RequestValidatorId),
95+
Scope: scope,
96+
},
97+
BlastPropagation: &sdp.BlastPropagation{
98+
// Deleting request validator will affect the method
99+
In: true,
100+
// Deleting method won't affect the request validator
101+
Out: false,
102+
},
103+
})
104+
}
105+
106+
for statusCode := range output.MethodResponses {
107+
if input.RestApiId != nil && input.ResourceId != nil && input.HttpMethod != nil {
108+
item.LinkedItemQueries = append(item.LinkedItemQueries, &sdp.LinkedItemQuery{
109+
Query: &sdp.Query{
110+
Type: "apigateway-method-response",
111+
Method: sdp.QueryMethod_GET,
112+
Query: fmt.Sprintf("%s/%s/%s/%s", *input.RestApiId, *input.ResourceId, *input.HttpMethod, statusCode),
113+
Scope: scope,
114+
},
115+
BlastPropagation: &sdp.BlastPropagation{
116+
// They are tightly coupled
117+
In: true,
118+
Out: true,
119+
},
120+
})
121+
}
122+
}
123+
124+
return item, nil
125+
}
126+
127+
func NewAPIGatewayMethodAdapter(client apigatewayClient, accountID string, region string) *adapterhelpers.AlwaysGetAdapter[*apigateway.GetMethodInput, *apigateway.GetMethodOutput, *apigateway.GetMethodInput, *apigateway.GetMethodOutput, apigatewayClient, *apigateway.Options] {
128+
return &adapterhelpers.AlwaysGetAdapter[*apigateway.GetMethodInput, *apigateway.GetMethodOutput, *apigateway.GetMethodInput, *apigateway.GetMethodOutput, apigatewayClient, *apigateway.Options]{
129+
ItemType: "apigateway-method",
130+
Client: client,
131+
AccountID: accountID,
132+
Region: region,
133+
AdapterMetadata: apiGatewayMethodAdapterMetadata,
134+
GetFunc: apiGatewayMethodGetFunc,
135+
GetInputMapper: func(scope, query string) *apigateway.GetMethodInput {
136+
// We are using a custom id of {rest-api-id}/{resource-id}/{http-method} e.g.
137+
// rest-api-id/resource-id/GET
138+
f := strings.Split(query, "/")
139+
if len(f) != 3 {
140+
return nil
141+
}
142+
143+
return &apigateway.GetMethodInput{
144+
RestApiId: &f[0],
145+
ResourceId: &f[1],
146+
HttpMethod: &f[2],
147+
}
148+
},
149+
DisableList: true,
150+
}
151+
}
152+
153+
var apiGatewayMethodAdapterMetadata = Metadata.Register(&sdp.AdapterMetadata{
154+
Type: "apigateway-method",
155+
DescriptiveName: "API Gateway Method",
156+
Category: sdp.AdapterCategory_ADAPTER_CATEGORY_NETWORK,
157+
SupportedQueryMethods: &sdp.AdapterSupportedQueryMethods{
158+
Get: true,
159+
GetDescription: "Get a Method by rest-api id, resource id and http-method",
160+
Search: true,
161+
SearchDescription: "Search Methods by ARN",
162+
},
163+
PotentialLinks: []string{
164+
"apigateway-integration",
165+
"apigateway-authorizer",
166+
"apigateway-request-validator",
167+
"apigateway-method-response",
168+
},
169+
})

0 commit comments

Comments
 (0)