Skip to content

Commit 119ed95

Browse files
committed
add apigateway integration adapter
1 parent 5858dc4 commit 119ed95

File tree

7 files changed

+299
-1
lines changed

7 files changed

+299
-1
lines changed

adapters/apigateway-integration.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
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+
type apiGatewayIntegrationGetter interface {
15+
GetIntegration(ctx context.Context, params *apigateway.GetIntegrationInput, optFns ...func(*apigateway.Options)) (*apigateway.GetIntegrationOutput, error)
16+
}
17+
18+
func apiGatewayIntegrationGetFunc(ctx context.Context, client apiGatewayIntegrationGetter, scope string, input *apigateway.GetIntegrationInput) (*sdp.Item, error) {
19+
output, err := client.GetIntegration(ctx, input)
20+
if err != nil {
21+
return nil, err
22+
}
23+
24+
attributes, err := adapterhelpers.ToAttributesWithExclude(output, "tags")
25+
if err != nil {
26+
return nil, err
27+
}
28+
29+
// We create a custom ID of {rest-api-id}/{resource-id}/{http-method} e.g.
30+
// rest-api-id/resource-id/GET
31+
integrationID := fmt.Sprintf(
32+
"%s/%s/%s",
33+
*input.RestApiId,
34+
*input.ResourceId,
35+
*input.HttpMethod,
36+
)
37+
err = attributes.Set("IntegrationID", integrationID)
38+
if err != nil {
39+
return nil, err
40+
}
41+
42+
item := &sdp.Item{
43+
Type: "apigateway-integration",
44+
UniqueAttribute: "IntegrationID",
45+
Attributes: attributes,
46+
Scope: scope,
47+
}
48+
49+
item.LinkedItemQueries = append(item.LinkedItemQueries, &sdp.LinkedItemQuery{
50+
Query: &sdp.Query{
51+
Type: "apigateway-method",
52+
Method: sdp.QueryMethod_GET,
53+
Query: fmt.Sprintf("%s/%s/%s", *input.RestApiId, *input.ResourceId, *input.HttpMethod),
54+
Scope: scope,
55+
},
56+
BlastPropagation: &sdp.BlastPropagation{
57+
// They are tightly coupled
58+
In: true,
59+
Out: true,
60+
},
61+
})
62+
63+
if output.ConnectionId != nil {
64+
item.LinkedItemQueries = append(item.LinkedItemQueries, &sdp.LinkedItemQuery{
65+
Query: &sdp.Query{
66+
Type: "apigateway-vpc-link",
67+
Method: sdp.QueryMethod_GET,
68+
Query: *output.ConnectionId,
69+
Scope: scope,
70+
},
71+
BlastPropagation: &sdp.BlastPropagation{
72+
// If VPC link goes away, so does the integration
73+
In: true,
74+
// If integration goes away, VPC link is still there
75+
Out: false,
76+
},
77+
})
78+
}
79+
80+
return item, nil
81+
}
82+
83+
func NewAPIGatewayIntegrationAdapter(client apiGatewayIntegrationGetter, accountID string, region string) *adapterhelpers.AlwaysGetAdapter[*apigateway.GetIntegrationInput, *apigateway.GetIntegrationOutput, *apigateway.GetIntegrationInput, *apigateway.GetIntegrationOutput, apiGatewayIntegrationGetter, *apigateway.Options] {
84+
return &adapterhelpers.AlwaysGetAdapter[*apigateway.GetIntegrationInput, *apigateway.GetIntegrationOutput, *apigateway.GetIntegrationInput, *apigateway.GetIntegrationOutput, apiGatewayIntegrationGetter, *apigateway.Options]{
85+
ItemType: "apigateway-integration",
86+
Client: client,
87+
AccountID: accountID,
88+
Region: region,
89+
AdapterMetadata: apiGatewayIntegrationAdapterMetadata,
90+
GetFunc: apiGatewayIntegrationGetFunc,
91+
GetInputMapper: func(scope, query string) *apigateway.GetIntegrationInput {
92+
// We are using a custom id of {rest-api-id}/{resource-id}/{http-method} e.g.
93+
// rest-api-id/resource-id/GET
94+
f := strings.Split(query, "/")
95+
if len(f) != 3 {
96+
slog.Error(
97+
"query must be in the format of: rest-api-id/resource-id/http-method",
98+
"found",
99+
query,
100+
)
101+
102+
return nil
103+
}
104+
105+
return &apigateway.GetIntegrationInput{
106+
RestApiId: &f[0],
107+
ResourceId: &f[1],
108+
HttpMethod: &f[2],
109+
}
110+
},
111+
DisableList: true,
112+
}
113+
}
114+
115+
var apiGatewayIntegrationAdapterMetadata = Metadata.Register(&sdp.AdapterMetadata{
116+
Type: "apigateway-integration",
117+
DescriptiveName: "API Gateway Integration",
118+
Category: sdp.AdapterCategory_ADAPTER_CATEGORY_NETWORK,
119+
SupportedQueryMethods: &sdp.AdapterSupportedQueryMethods{
120+
Get: true,
121+
GetDescription: "Get an Integration by rest-api id, resource id, and http-method",
122+
Search: true,
123+
SearchDescription: "Search Integrations by ARN",
124+
},
125+
})
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
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/aws/aws-sdk-go-v2/service/apigateway/types"
12+
"github.com/overmindtech/aws-source/adapterhelpers"
13+
"github.com/overmindtech/sdp-go"
14+
)
15+
16+
type mockAPIGatewayIntegrationClient struct{}
17+
18+
func (m *mockAPIGatewayIntegrationClient) GetIntegration(ctx context.Context, params *apigateway.GetIntegrationInput, optFns ...func(*apigateway.Options)) (*apigateway.GetIntegrationOutput, error) {
19+
return &apigateway.GetIntegrationOutput{
20+
IntegrationResponses: map[string]types.IntegrationResponse{
21+
"200": {
22+
ResponseTemplates: map[string]string{
23+
"application/json": "",
24+
},
25+
StatusCode: aws.String("200"),
26+
},
27+
},
28+
CacheKeyParameters: []string{},
29+
Uri: aws.String("arn:aws:apigateway:us-west-2:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-2:123412341234:function:My_Function/invocations"),
30+
HttpMethod: aws.String("POST"),
31+
CacheNamespace: aws.String("y9h6rt"),
32+
Type: "AWS",
33+
ConnectionId: aws.String("vpc-connection-id"),
34+
}, nil
35+
}
36+
37+
func TestApiGatewayIntegrationGetFunc(t *testing.T) {
38+
ctx := context.Background()
39+
cli := mockAPIGatewayIntegrationClient{}
40+
41+
input := &apigateway.GetIntegrationInput{
42+
RestApiId: aws.String("rest-api-id"),
43+
ResourceId: aws.String("resource-id"),
44+
HttpMethod: aws.String("GET"),
45+
}
46+
47+
item, err := apiGatewayIntegrationGetFunc(ctx, &cli, "scope", input)
48+
if err != nil {
49+
t.Fatalf("unexpected error: %v", err)
50+
}
51+
52+
if err = item.Validate(); err != nil {
53+
t.Fatal(err)
54+
}
55+
56+
integrationID := fmt.Sprintf("%s/%s/%s", *input.RestApiId, *input.ResourceId, *input.HttpMethod)
57+
58+
tests := adapterhelpers.QueryTests{
59+
{
60+
ExpectedType: "apigateway-method",
61+
ExpectedMethod: sdp.QueryMethod_GET,
62+
ExpectedQuery: integrationID,
63+
ExpectedScope: "scope",
64+
},
65+
{
66+
ExpectedType: "apigateway-vpc-link",
67+
ExpectedMethod: sdp.QueryMethod_GET,
68+
ExpectedQuery: "vpc-connection-id",
69+
ExpectedScope: "scope",
70+
},
71+
}
72+
73+
tests.Execute(t, item)
74+
}
75+
76+
func TestNewAPIGatewayIntegrationAdapter(t *testing.T) {
77+
config, account, region := adapterhelpers.GetAutoConfig(t)
78+
79+
client := apigateway.NewFromConfig(config)
80+
81+
adapter := NewAPIGatewayIntegrationAdapter(client, account, region)
82+
83+
test := adapterhelpers.E2ETest{
84+
Adapter: adapter,
85+
Timeout: 10 * time.Second,
86+
SkipList: true,
87+
}
88+
89+
test.Run(t)
90+
}

adapters/integration/apigateway/apigateway_test.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func APIGateway(t *testing.T) {
2727

2828
accountID := testAWSConfig.AccountID
2929

30-
t.Log("Running APIGateway integration test")
30+
t.Log("Running APIGateway itgr test")
3131

3232
restApiSource := adapters.NewAPIGatewayRestApiAdapter(testClient, accountID, testAWSConfig.Region)
3333

@@ -57,6 +57,13 @@ func APIGateway(t *testing.T) {
5757
t.Fatalf("failed to validate APIGateway method response adapter: %v", err)
5858
}
5959

60+
integrationSource := adapters.NewAPIGatewayIntegrationAdapter(testClient, accountID, testAWSConfig.Region)
61+
62+
err = integrationSource.Validate()
63+
if err != nil {
64+
t.Fatalf("failed to validate APIGateway itgr adapter: %v", err)
65+
}
66+
6067
scope := adapterhelpers.FormatScope(accountID, testAWSConfig.Region)
6168

6269
// List restApis
@@ -203,5 +210,21 @@ func APIGateway(t *testing.T) {
203210
t.Fatalf("expected method response ID %s, got %s", methodResponseID, uniqueMethodResponseAttr)
204211
}
205212

213+
// Get integration
214+
integrationID := fmt.Sprintf("%s/GET", resourceUniqueAttrFromGet) // resourceUniqueAttribute contains the restApiID
215+
itgr, err := integrationSource.Get(ctx, scope, integrationID, true)
216+
if err != nil {
217+
t.Fatalf("failed to get APIGateway itgr: %v", err)
218+
}
219+
220+
uniqueIntegrationAttr, err := itgr.GetAttributes().Get(itgr.GetUniqueAttribute())
221+
if err != nil {
222+
t.Fatalf("failed to get unique itgr attribute: %v", err)
223+
}
224+
225+
if uniqueIntegrationAttr != integrationID {
226+
t.Fatalf("expected integration ID %s, got %s", integrationID, uniqueIntegrationAttr)
227+
}
228+
206229
t.Log("APIGateway integration test completed")
207230
}

adapters/integration/apigateway/create.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,32 @@ func createMethodResponse(ctx context.Context, logger *slog.Logger, client *apig
139139

140140
return nil
141141
}
142+
143+
func createIntegration(ctx context.Context, logger *slog.Logger, client *apigateway.Client, restAPIID, resourceID *string, method string) error {
144+
// check if an integration with the same method already exists
145+
err := findIntegration(ctx, client, restAPIID, resourceID, method)
146+
if err != nil {
147+
if errors.As(err, new(integration.NotFoundError)) {
148+
logger.InfoContext(ctx, "Creating integration")
149+
} else {
150+
return err
151+
}
152+
}
153+
154+
if err == nil {
155+
logger.InfoContext(ctx, "Integration already exists")
156+
return nil
157+
}
158+
159+
_, err = client.PutIntegration(ctx, &apigateway.PutIntegrationInput{
160+
RestApiId: restAPIID,
161+
ResourceId: resourceID,
162+
HttpMethod: adapterhelpers.PtrString(method),
163+
Type: "MOCK",
164+
})
165+
if err != nil {
166+
return err
167+
}
168+
169+
return nil
170+
}

adapters/integration/apigateway/find.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,26 @@ func findMethodResponse(ctx context.Context, client *apigateway.Client, restAPII
8787

8888
return nil
8989
}
90+
91+
func findIntegration(ctx context.Context, client *apigateway.Client, restAPIID, resourceID *string, method string) error {
92+
_, err := client.GetIntegration(ctx, &apigateway.GetIntegrationInput{
93+
RestApiId: restAPIID,
94+
ResourceId: resourceID,
95+
HttpMethod: &method,
96+
})
97+
98+
if err != nil {
99+
var notFoundErr *types.NotFoundException
100+
if errors.As(err, &notFoundErr) {
101+
return integration.NewNotFoundError(integration.ResourceName(
102+
integration.APIGateway,
103+
integrationSrc,
104+
method,
105+
))
106+
}
107+
108+
return err
109+
}
110+
111+
return nil
112+
}

adapters/integration/apigateway/setup.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const (
1313
resourceSrc = "resource"
1414
methodSrc = "method"
1515
methodResponseSrc = "method-response"
16+
integrationSrc = "integration"
1617
)
1718

1819
func setup(ctx context.Context, logger *slog.Logger, client *apigateway.Client) error {
@@ -48,5 +49,11 @@ func setup(ctx context.Context, logger *slog.Logger, client *apigateway.Client)
4849
return err
4950
}
5051

52+
// Create integration
53+
err = createIntegration(ctx, logger, client, restApiID, testResourceID, "GET")
54+
if err != nil {
55+
return err
56+
}
57+
5158
return nil
5259
}

proc/proc.go

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

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

0 commit comments

Comments
 (0)