Skip to content

Commit 6ea27e5

Browse files
committed
add apigateway deployment adapter
1 parent b8856b8 commit 6ea27e5

File tree

7 files changed

+299
-0
lines changed

7 files changed

+299
-0
lines changed

adapters/apigateway-deployment.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
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/aws/aws-sdk-go-v2/service/apigateway/types"
10+
"github.com/overmindtech/aws-source/adapterhelpers"
11+
"github.com/overmindtech/sdp-go"
12+
)
13+
14+
// convertGetDeploymentOutputToDeployment converts a GetDeploymentOutput to a Deployment
15+
func convertGetDeploymentOutputToDeployment(output *apigateway.GetDeploymentOutput) *types.Deployment {
16+
return &types.Deployment{
17+
Id: output.Id,
18+
CreatedDate: output.CreatedDate,
19+
Description: output.Description,
20+
ApiSummary: output.ApiSummary,
21+
}
22+
}
23+
24+
func deploymentOutputMapper(scope string, awsItem *types.Deployment) (*sdp.Item, error) {
25+
attributes, err := adapterhelpers.ToAttributesWithExclude(awsItem, "tags")
26+
if err != nil {
27+
return nil, err
28+
}
29+
30+
item := sdp.Item{
31+
Type: "apigateway-deployment",
32+
UniqueAttribute: "Id",
33+
Attributes: attributes,
34+
Scope: scope,
35+
}
36+
37+
return &item, nil
38+
}
39+
40+
func NewAPIGatewayDeploymentAdapter(client *apigateway.Client, accountID string, region string) *adapterhelpers.GetListAdapter[*types.Deployment, *apigateway.Client, *apigateway.Options] {
41+
return &adapterhelpers.GetListAdapter[*types.Deployment, *apigateway.Client, *apigateway.Options]{
42+
ItemType: "apigateway-deployment",
43+
Client: client,
44+
AccountID: accountID,
45+
Region: region,
46+
AdapterMetadata: deploymentAdapterMetadata,
47+
GetFunc: func(ctx context.Context, client *apigateway.Client, scope, query string) (*types.Deployment, error) {
48+
f := strings.Split(query, "/")
49+
if len(f) != 2 {
50+
return nil, &sdp.QueryError{
51+
ErrorType: sdp.QueryError_NOTFOUND,
52+
ErrorString: fmt.Sprintf("query must be in the format of: the rest-api-id/deployment-id, but found: %s", query),
53+
}
54+
}
55+
out, err := client.GetDeployment(ctx, &apigateway.GetDeploymentInput{
56+
RestApiId: &f[0],
57+
DeploymentId: &f[1],
58+
})
59+
if err != nil {
60+
return nil, err
61+
}
62+
return convertGetDeploymentOutputToDeployment(out), nil
63+
},
64+
DisableList: true,
65+
SearchFunc: func(ctx context.Context, client *apigateway.Client, scope string, query string) ([]*types.Deployment, error) {
66+
f := strings.Split(query, "/")
67+
var restAPIID string
68+
var description string
69+
70+
switch len(f) {
71+
case 1:
72+
restAPIID = f[0]
73+
case 2:
74+
restAPIID = f[0]
75+
description = f[1]
76+
default:
77+
return nil, &sdp.QueryError{
78+
ErrorType: sdp.QueryError_NOTFOUND,
79+
ErrorString: fmt.Sprintf(
80+
"query must be in the format of: the rest-api-id/deployment-id or rest-api-id, but found: %s",
81+
query,
82+
),
83+
}
84+
}
85+
86+
out, err := client.GetDeployments(ctx, &apigateway.GetDeploymentsInput{
87+
RestApiId: &restAPIID,
88+
})
89+
if err != nil {
90+
return nil, err
91+
}
92+
93+
var items []*types.Deployment
94+
for _, deployment := range out.Items {
95+
if description != "" && strings.Contains(*deployment.Description, description) {
96+
items = append(items, &deployment)
97+
} else {
98+
items = append(items, &deployment)
99+
}
100+
}
101+
102+
return items, nil
103+
},
104+
ItemMapper: func(_, scope string, awsItem *types.Deployment) (*sdp.Item, error) {
105+
return deploymentOutputMapper(scope, awsItem)
106+
},
107+
}
108+
}
109+
110+
var deploymentAdapterMetadata = Metadata.Register(&sdp.AdapterMetadata{
111+
Type: "apigateway-deployment",
112+
DescriptiveName: "API Gateway Deployment",
113+
Category: sdp.AdapterCategory_ADAPTER_CATEGORY_CONFIGURATION,
114+
SupportedQueryMethods: &sdp.AdapterSupportedQueryMethods{
115+
Get: true,
116+
Search: true,
117+
GetDescription: "Get an API Gateway Deployment by its rest API ID and ID: rest-api-id/deployment-id",
118+
SearchDescription: "Search for API Gateway Deployments by their rest API ID or with rest API ID and their description: rest-api-id/deployment-description",
119+
},
120+
})
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package adapters
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/aws/aws-sdk-go-v2/aws"
8+
"github.com/aws/aws-sdk-go-v2/service/apigateway"
9+
"github.com/aws/aws-sdk-go-v2/service/apigateway/types"
10+
"github.com/overmindtech/aws-source/adapterhelpers"
11+
)
12+
13+
func TestDeploymentOutputMapper(t *testing.T) {
14+
awsItem := &types.Deployment{
15+
Id: aws.String("deployment-id"),
16+
CreatedDate: aws.Time(time.Now()),
17+
Description: aws.String("deployment-description"),
18+
ApiSummary: map[string]map[string]types.MethodSnapshot{},
19+
}
20+
21+
item, err := deploymentOutputMapper("scope", awsItem)
22+
if err != nil {
23+
t.Fatalf("unexpected error: %v", err)
24+
}
25+
26+
if err := item.Validate(); err != nil {
27+
t.Error(err)
28+
}
29+
}
30+
31+
func TestNewAPIGatewayDeploymentAdapter(t *testing.T) {
32+
config, account, region := adapterhelpers.GetAutoConfig(t)
33+
34+
client := apigateway.NewFromConfig(config)
35+
36+
adapter := NewAPIGatewayDeploymentAdapter(client, account, region)
37+
38+
test := adapterhelpers.E2ETest{
39+
Adapter: adapter,
40+
Timeout: 10 * time.Second,
41+
SkipList: true,
42+
}
43+
44+
test.Run(t)
45+
}

adapters/integration/apigateway/apigateway_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ func APIGateway(t *testing.T) {
2929

3030
t.Log("Running APIGateway integration test")
3131

32+
// Resources ------------------------------------------------------------------------------------------------------
33+
3234
restApiSource := adapters.NewAPIGatewayRestApiAdapter(testClient, accountID, testAWSConfig.Region)
3335

3436
err = restApiSource.Validate()
@@ -78,6 +80,15 @@ func APIGateway(t *testing.T) {
7880
t.Fatalf("failed to validate APIGateway authorizer adapter: %v", err)
7981
}
8082

83+
deploymentSource := adapters.NewAPIGatewayDeploymentAdapter(testClient, accountID, testAWSConfig.Region)
84+
85+
err = deploymentSource.Validate()
86+
if err != nil {
87+
t.Fatalf("failed to validate APIGateway deployment adapter: %v", err)
88+
}
89+
90+
// Tests ----------------------------------------------------------------------------------------------------------
91+
8192
scope := adapterhelpers.FormatScope(accountID, testAWSConfig.Region)
8293

8394
// List restApis
@@ -376,5 +387,76 @@ func APIGateway(t *testing.T) {
376387
t.Fatalf("expected authorizer ID %s, got %s", authorizerID, authorizerIDFromSearch)
377388
}
378389

390+
// Search deployments by restApiID
391+
deployments, err := deploymentSource.Search(ctx, scope, restApiID, true)
392+
if err != nil {
393+
t.Fatalf("failed to search APIGateway deployments: %v", err)
394+
}
395+
396+
if len(deployments) == 0 {
397+
t.Fatalf("no deployments found")
398+
}
399+
400+
deploymentUniqueAttribute := deployments[0].GetUniqueAttribute()
401+
402+
deploymentID, err := integration.GetUniqueAttributeValueBySignificantAttribute(
403+
deploymentUniqueAttribute,
404+
"Description",
405+
"test-deployment",
406+
deployments,
407+
true,
408+
)
409+
if err != nil {
410+
t.Fatalf("failed to get deployment ID: %v", err)
411+
}
412+
413+
// Get deployment
414+
query = fmt.Sprintf("%s/%s", restApiID, deploymentID)
415+
deployment, err := deploymentSource.Get(ctx, scope, query, true)
416+
if err != nil {
417+
t.Fatalf("failed to get APIGateway deployment: %v", err)
418+
}
419+
420+
deploymentIDFromGet, err := integration.GetUniqueAttributeValueBySignificantAttribute(
421+
deploymentUniqueAttribute,
422+
"Description",
423+
"test-deployment",
424+
[]*sdp.Item{deployment},
425+
true,
426+
)
427+
if err != nil {
428+
t.Fatalf("failed to get deployment ID from get: %v", err)
429+
}
430+
431+
if deploymentID != deploymentIDFromGet {
432+
t.Fatalf("expected deployment ID %s, got %s", deploymentID, deploymentIDFromGet)
433+
}
434+
435+
// Search deployment by restApiID/description
436+
query = fmt.Sprintf("%s/test-deployment", restApiID)
437+
deploymentsFromSearch, err := deploymentSource.Search(ctx, scope, query, true)
438+
if err != nil {
439+
t.Fatalf("failed to search APIGateway deployments: %v", err)
440+
}
441+
442+
if len(deploymentsFromSearch) == 0 {
443+
t.Fatalf("no deployments found")
444+
}
445+
446+
deploymentIDFromSearch, err := integration.GetUniqueAttributeValueBySignificantAttribute(
447+
deploymentUniqueAttribute,
448+
"Description",
449+
"test-deployment",
450+
deploymentsFromSearch,
451+
true,
452+
)
453+
if err != nil {
454+
t.Fatalf("failed to get deployment ID from search: %v", err)
455+
}
456+
457+
if deploymentID != deploymentIDFromSearch {
458+
t.Fatalf("expected deployment ID %s, got %s", deploymentID, deploymentIDFromSearch)
459+
}
460+
379461
t.Log("APIGateway integration test completed")
380462
}

adapters/integration/apigateway/create.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,3 +228,30 @@ func createAuthorizer(ctx context.Context, logger *slog.Logger, client *apigatew
228228

229229
return nil
230230
}
231+
232+
func createDeployment(ctx context.Context, logger *slog.Logger, client *apigateway.Client, restAPIID string) error {
233+
// check if a deployment with the same name already exists
234+
id, err := findDeploymentByDescription(ctx, client, restAPIID, "test-deployment")
235+
if err != nil {
236+
if errors.As(err, new(integration.NotFoundError)) {
237+
logger.InfoContext(ctx, "Creating deployment")
238+
} else {
239+
return err
240+
}
241+
}
242+
243+
if id != nil {
244+
logger.InfoContext(ctx, "Deployment already exists")
245+
return nil
246+
}
247+
248+
_, err = client.CreateDeployment(ctx, &apigateway.CreateDeploymentInput{
249+
RestApiId: &restAPIID,
250+
Description: adapterhelpers.PtrString("test-deployment"),
251+
})
252+
if err != nil {
253+
return err
254+
}
255+
256+
return nil
257+
}

adapters/integration/apigateway/find.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,3 +152,20 @@ func findAuthorizerByName(ctx context.Context, client *apigateway.Client, restAP
152152

153153
return nil, integration.NewNotFoundError(integration.ResourceName(integration.APIGateway, authorizerSrc, name))
154154
}
155+
156+
func findDeploymentByDescription(ctx context.Context, client *apigateway.Client, restAPIID, description string) (*string, error) {
157+
result, err := client.GetDeployments(ctx, &apigateway.GetDeploymentsInput{
158+
RestApiId: &restAPIID,
159+
})
160+
if err != nil {
161+
return nil, err
162+
}
163+
164+
for _, deployment := range result.Items {
165+
if *deployment.Description == description {
166+
return deployment.Id, nil
167+
}
168+
}
169+
170+
return nil, integration.NewNotFoundError(integration.ResourceName(integration.APIGateway, deploymentSrc, description))
171+
}

adapters/integration/apigateway/setup.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const (
1616
integrationSrc = "integration"
1717
apiKeySrc = "api-key"
1818
authorizerSrc = "authorizer"
19+
deploymentSrc = "deployment"
1920
)
2021

2122
func setup(ctx context.Context, logger *slog.Logger, client *apigateway.Client) error {
@@ -69,5 +70,11 @@ func setup(ctx context.Context, logger *slog.Logger, client *apigateway.Client)
6970
return err
7071
}
7172

73+
// Create Deployment
74+
err = createDeployment(ctx, logger, client, *restApiID)
75+
if err != nil {
76+
return err
77+
}
78+
7279
return nil
7380
}

proc/proc.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,7 @@ func InitializeAwsSourceEngine(ctx context.Context, ec *discovery.EngineConfig,
485485
adapters.NewAPIGatewayVpcLinkAdapter(apigatewayClient, *callerID.Account, cfg.Region),
486486
adapters.NewAPIGatewayApiKeyAdapter(apigatewayClient, *callerID.Account, cfg.Region),
487487
adapters.NewAPIGatewayAuthorizerAdapter(apigatewayClient, *callerID.Account, cfg.Region),
488+
adapters.NewAPIGatewayDeploymentAdapter(apigatewayClient, *callerID.Account, cfg.Region),
488489

489490
// SSM
490491
adapters.NewSSMParameterAdapter(ssmClient, *callerID.Account, cfg.Region),

0 commit comments

Comments
 (0)