Skip to content

Commit 5858dc4

Browse files
committed
add apigateway method response adapter
1 parent 5e95b2b commit 5858dc4

File tree

8 files changed

+275
-4
lines changed

8 files changed

+275
-4
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+
"log/slog"
7+
"strings"
8+
9+
"github.com/aws/aws-sdk-go-v2/service/apigateway"
10+
"github.com/overmindtech/aws-source/adapterhelpers"
11+
"github.com/overmindtech/sdp-go"
12+
)
13+
14+
func apiGatewayMethodResponseGetFunc(ctx context.Context, client apigatewayClient, scope string, input *apigateway.GetMethodResponseInput) (*sdp.Item, error) {
15+
output, err := client.GetMethodResponse(ctx, input)
16+
if err != nil {
17+
return nil, err
18+
}
19+
20+
attributes, err := adapterhelpers.ToAttributesWithExclude(output, "tags")
21+
if err != nil {
22+
return nil, err
23+
}
24+
25+
// We create a custom ID of {rest-api-id}/{resource-id}/{http-method}/{status-code} e.g.
26+
// rest-api-id/resource-id/GET/200
27+
methodResponseID := fmt.Sprintf(
28+
"%s/%s/%s/%s",
29+
*input.RestApiId,
30+
*input.ResourceId,
31+
*input.HttpMethod,
32+
*input.StatusCode,
33+
)
34+
err = attributes.Set("MethodResponseID", methodResponseID)
35+
if err != nil {
36+
return nil, err
37+
}
38+
39+
item := &sdp.Item{
40+
Type: "apigateway-method-response",
41+
UniqueAttribute: "MethodResponseID",
42+
Attributes: attributes,
43+
Scope: scope,
44+
}
45+
46+
item.LinkedItemQueries = append(item.LinkedItemQueries, &sdp.LinkedItemQuery{
47+
Query: &sdp.Query{
48+
Type: "apigateway-method",
49+
Method: sdp.QueryMethod_GET,
50+
Query: fmt.Sprintf("%s/%s/%s", *input.RestApiId, *input.ResourceId, *input.HttpMethod),
51+
Scope: scope,
52+
},
53+
BlastPropagation: &sdp.BlastPropagation{
54+
// They are tightly coupled
55+
In: true,
56+
Out: true,
57+
},
58+
})
59+
60+
return item, nil
61+
}
62+
63+
func NewAPIGatewayMethodResponseAdapter(client apigatewayClient, accountID string, region string) *adapterhelpers.AlwaysGetAdapter[*apigateway.GetMethodResponseInput, *apigateway.GetMethodResponseOutput, *apigateway.GetMethodResponseInput, *apigateway.GetMethodResponseOutput, apigatewayClient, *apigateway.Options] {
64+
return &adapterhelpers.AlwaysGetAdapter[*apigateway.GetMethodResponseInput, *apigateway.GetMethodResponseOutput, *apigateway.GetMethodResponseInput, *apigateway.GetMethodResponseOutput, apigatewayClient, *apigateway.Options]{
65+
ItemType: "apigateway-method-response",
66+
Client: client,
67+
AccountID: accountID,
68+
Region: region,
69+
AdapterMetadata: apiGatewayMethodResponseAdapterMetadata,
70+
GetFunc: apiGatewayMethodResponseGetFunc,
71+
GetInputMapper: func(scope, query string) *apigateway.GetMethodResponseInput {
72+
// We are using a custom id of {rest-api-id}/{resource-id}/{http-method}/{status-code} e.g.
73+
// rest-api-id/resource-id/GET/200
74+
f := strings.Split(query, "/")
75+
if len(f) != 4 {
76+
slog.Error(
77+
"query must be in the format of: rest-api-id/resource-id/http-method/status-code",
78+
"found",
79+
query,
80+
)
81+
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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
type apigatewayClient interface {
1515
GetMethod(ctx context.Context, params *apigateway.GetMethodInput, optFns ...func(*apigateway.Options)) (*apigateway.GetMethodOutput, error)
16+
GetMethodResponse(ctx context.Context, params *apigateway.GetMethodResponseInput, optFns ...func(*apigateway.Options)) (*apigateway.GetMethodResponseOutput, error)
1617
}
1718

1819
func apiGatewayMethodGetFunc(ctx context.Context, client apigatewayClient, scope string, input *apigateway.GetMethodInput) (*sdp.Item, error) {
@@ -141,5 +142,4 @@ var apiGatewayMethodAdapterMetadata = Metadata.Register(&sdp.AdapterMetadata{
141142
Search: true,
142143
SearchDescription: "Search Methods by ARN",
143144
},
144-
PotentialLinks: []string{"apigateway-method-response"},
145145
})

adapters/integration/apigateway/apigateway_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ func APIGateway(t *testing.T) {
5050
t.Fatalf("failed to validate APIGateway method adapter: %v", err)
5151
}
5252

53+
methodResponseSource := adapters.NewAPIGatewayMethodResponseAdapter(testClient, accountID, testAWSConfig.Region)
54+
55+
err = methodResponseSource.Validate()
56+
if err != nil {
57+
t.Fatalf("failed to validate APIGateway method response adapter: %v", err)
58+
}
59+
5360
scope := adapterhelpers.FormatScope(accountID, testAWSConfig.Region)
5461

5562
// List restApis
@@ -179,4 +186,22 @@ func APIGateway(t *testing.T) {
179186
if uniqueMethodAttr != methodID {
180187
t.Fatalf("expected method ID %s, got %s", methodID, uniqueMethodAttr)
181188
}
189+
190+
// Get method response
191+
methodResponseID := fmt.Sprintf("%s/200", methodID)
192+
methodResponse, err := methodResponseSource.Get(ctx, scope, methodResponseID, true)
193+
if err != nil {
194+
t.Fatalf("failed to get APIGateway method response: %v", err)
195+
}
196+
197+
uniqueMethodResponseAttr, err := methodResponse.GetAttributes().Get(methodResponse.GetUniqueAttribute())
198+
if err != nil {
199+
t.Fatalf("failed to get unique method response attribute: %v", err)
200+
}
201+
202+
if uniqueMethodResponseAttr != methodResponseID {
203+
t.Fatalf("expected method response ID %s, got %s", methodResponseID, uniqueMethodResponseAttr)
204+
}
205+
206+
t.Log("APIGateway integration test completed")
182207
}

adapters/integration/apigateway/create.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,38 @@ func createMethod(ctx context.Context, logger *slog.Logger, client *apigateway.C
104104

105105
return nil
106106
}
107+
108+
func createMethodResponse(ctx context.Context, logger *slog.Logger, client *apigateway.Client, restAPIID, resourceID *string, method, statusCode string) error {
109+
// check if a method response with the same status code already exists
110+
err := findMethodResponse(ctx, client, restAPIID, resourceID, method, statusCode)
111+
if err != nil {
112+
if errors.As(err, new(integration.NotFoundError)) {
113+
logger.InfoContext(ctx, "Creating method response")
114+
} else {
115+
return err
116+
}
117+
}
118+
119+
if err == nil {
120+
logger.InfoContext(ctx, "Method response already exists")
121+
return nil
122+
}
123+
124+
_, err = client.PutMethodResponse(ctx, &apigateway.PutMethodResponseInput{
125+
RestApiId: restAPIID,
126+
ResourceId: resourceID,
127+
HttpMethod: adapterhelpers.PtrString(method),
128+
StatusCode: adapterhelpers.PtrString(statusCode),
129+
ResponseModels: map[string]string{
130+
"application/json": "Empty",
131+
},
132+
ResponseParameters: map[string]bool{
133+
"method.response.header.Content-Type": true,
134+
},
135+
})
136+
if err != nil {
137+
return err
138+
}
139+
140+
return nil
141+
}

adapters/integration/apigateway/find.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,28 @@ func findMethod(ctx context.Context, client *apigateway.Client, restAPIID, resou
6262

6363
return nil
6464
}
65+
66+
func findMethodResponse(ctx context.Context, client *apigateway.Client, restAPIID, resourceID *string, method string, statusCode string) error {
67+
_, err := client.GetMethodResponse(ctx, &apigateway.GetMethodResponseInput{
68+
RestApiId: restAPIID,
69+
ResourceId: resourceID,
70+
HttpMethod: &method,
71+
StatusCode: &statusCode,
72+
})
73+
74+
if err != nil {
75+
var notFoundErr *types.NotFoundException
76+
if errors.As(err, &notFoundErr) {
77+
return integration.NewNotFoundError(integration.ResourceName(
78+
integration.APIGateway,
79+
methodResponseSrc,
80+
method,
81+
statusCode,
82+
))
83+
}
84+
85+
return err
86+
}
87+
88+
return nil
89+
}

adapters/integration/apigateway/setup.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ import (
99
)
1010

1111
const (
12-
restAPISrc = "rest-api"
13-
resourceSrc = "resource"
14-
methodSrc = "method"
12+
restAPISrc = "rest-api"
13+
resourceSrc = "resource"
14+
methodSrc = "method"
15+
methodResponseSrc = "method-response"
1516
)
1617

1718
func setup(ctx context.Context, logger *slog.Logger, client *apigateway.Client) error {
@@ -41,5 +42,11 @@ func setup(ctx context.Context, logger *slog.Logger, client *apigateway.Client)
4142
return err
4243
}
4344

45+
// Create method response
46+
err = createMethodResponse(ctx, logger, client, restApiID, testResourceID, "GET", "200")
47+
if err != nil {
48+
return err
49+
}
50+
4451
return nil
4552
}

proc/proc.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,7 @@ func InitializeAwsSourceEngine(ctx context.Context, ec *discovery.EngineConfig,
480480
adapters.NewAPIGatewayResourceAdapter(apigatewayClient, *callerID.Account, cfg.Region),
481481
adapters.NewAPIGatewayDomainNameAdapter(apigatewayClient, *callerID.Account, cfg.Region),
482482
adapters.NewAPIGatewayMethodAdapter(apigatewayClient, *callerID.Account, cfg.Region),
483+
adapters.NewAPIGatewayMethodResponseAdapter(apigatewayClient, *callerID.Account, cfg.Region),
483484

484485
// SSM
485486
adapters.NewSSMParameterAdapter(ssmClient, *callerID.Account, cfg.Region),

0 commit comments

Comments
 (0)