Skip to content

Commit a5b9e53

Browse files
committed
add apigateway api key adapter
1 parent f53f262 commit a5b9e53

File tree

10 files changed

+353
-4
lines changed

10 files changed

+353
-4
lines changed

adapters/apigateway-api-key.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package adapters
2+
3+
import (
4+
"context"
5+
"github.com/aws/aws-sdk-go-v2/service/apigateway"
6+
"github.com/aws/aws-sdk-go-v2/service/apigateway/types"
7+
"github.com/overmindtech/aws-source/adapterhelpers"
8+
"github.com/overmindtech/sdp-go"
9+
"strings"
10+
)
11+
12+
// convertGetApiKeyOutputToApiKey converts a GetApiKeyOutput to an ApiKey
13+
func convertGetApiKeyOutputToApiKey(output *apigateway.GetApiKeyOutput) *types.ApiKey {
14+
return &types.ApiKey{
15+
Id: output.Id,
16+
Name: output.Name,
17+
Enabled: output.Enabled,
18+
CreatedDate: output.CreatedDate,
19+
LastUpdatedDate: output.LastUpdatedDate,
20+
StageKeys: output.StageKeys,
21+
Tags: output.Tags,
22+
}
23+
}
24+
25+
func apiKeyListFunc(ctx context.Context, client *apigateway.Client, _ string) ([]*types.ApiKey, error) {
26+
out, err := client.GetApiKeys(ctx, &apigateway.GetApiKeysInput{})
27+
if err != nil {
28+
return nil, err
29+
}
30+
31+
var items []*types.ApiKey
32+
for _, apiKey := range out.Items {
33+
items = append(items, &apiKey)
34+
}
35+
36+
return items, nil
37+
}
38+
39+
func apiKeyOutputMapper(scope string, awsItem *types.ApiKey) (*sdp.Item, error) {
40+
attributes, err := adapterhelpers.ToAttributesWithExclude(awsItem, "tags")
41+
if err != nil {
42+
return nil, err
43+
}
44+
45+
item := sdp.Item{
46+
Type: "apigateway-api-key",
47+
UniqueAttribute: "Id",
48+
Attributes: attributes,
49+
Scope: scope,
50+
Tags: awsItem.Tags,
51+
}
52+
53+
for _, key := range awsItem.StageKeys {
54+
// {restApiId}/{stage}
55+
restAPIID := strings.Split(key, "/")[0]
56+
if restAPIID != "" {
57+
item.LinkedItemQueries = append(item.LinkedItemQueries, &sdp.LinkedItemQuery{
58+
Query: &sdp.Query{
59+
Type: "apigateway-rest-api",
60+
Method: sdp.QueryMethod_GET,
61+
Query: restAPIID,
62+
Scope: scope,
63+
},
64+
BlastPropagation: &sdp.BlastPropagation{
65+
// They are tightly coupled, so we need to propagate both ways
66+
In: true,
67+
Out: true,
68+
},
69+
})
70+
}
71+
}
72+
73+
return &item, nil
74+
}
75+
76+
func NewAPIGatewayApiKeyAdapter(client *apigateway.Client, accountID string, region string) *adapterhelpers.GetListAdapter[*types.ApiKey, *apigateway.Client, *apigateway.Options] {
77+
return &adapterhelpers.GetListAdapter[*types.ApiKey, *apigateway.Client, *apigateway.Options]{
78+
ItemType: "apigateway-api-key",
79+
Client: client,
80+
AccountID: accountID,
81+
Region: region,
82+
AdapterMetadata: apiKeyAdapterMetadata,
83+
GetFunc: func(ctx context.Context, client *apigateway.Client, scope, query string) (*types.ApiKey, error) {
84+
out, err := client.GetApiKey(ctx, &apigateway.GetApiKeyInput{
85+
ApiKey: &query,
86+
})
87+
if err != nil {
88+
return nil, err
89+
}
90+
return convertGetApiKeyOutputToApiKey(out), nil
91+
},
92+
ListFunc: apiKeyListFunc,
93+
SearchFunc: func(ctx context.Context, client *apigateway.Client, scope string, query string) ([]*types.ApiKey, error) {
94+
out, err := client.GetApiKeys(ctx, &apigateway.GetApiKeysInput{
95+
NameQuery: &query,
96+
})
97+
if err != nil {
98+
return nil, err
99+
}
100+
101+
var items []*types.ApiKey
102+
for _, apiKey := range out.Items {
103+
items = append(items, &apiKey)
104+
}
105+
106+
return items, nil
107+
},
108+
ItemMapper: func(_, scope string, awsItem *types.ApiKey) (*sdp.Item, error) {
109+
return apiKeyOutputMapper(scope, awsItem)
110+
},
111+
}
112+
}
113+
114+
var apiKeyAdapterMetadata = Metadata.Register(&sdp.AdapterMetadata{
115+
Type: "apigateway-api-key",
116+
DescriptiveName: "API Key",
117+
Category: sdp.AdapterCategory_ADAPTER_CATEGORY_SECURITY,
118+
SupportedQueryMethods: &sdp.AdapterSupportedQueryMethods{
119+
Get: true,
120+
List: true,
121+
Search: true,
122+
GetDescription: "Get an API Key by ID",
123+
ListDescription: "List all API Keys",
124+
SearchDescription: "Search for API Keys by their name",
125+
},
126+
})
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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+
"github.com/overmindtech/sdp-go"
12+
)
13+
14+
func TestApiKeyOutputMapper(t *testing.T) {
15+
awsItem := &types.ApiKey{
16+
Id: aws.String("api-key-id"),
17+
Name: aws.String("api-key-name"),
18+
Enabled: true,
19+
CreatedDate: aws.Time(time.Now()),
20+
LastUpdatedDate: aws.Time(time.Now()),
21+
StageKeys: []string{"rest-api-id/stage"},
22+
Tags: map[string]string{"key": "value"},
23+
}
24+
25+
item, err := apiKeyOutputMapper("scope", awsItem)
26+
if err != nil {
27+
t.Fatalf("unexpected error: %v", err)
28+
}
29+
30+
if err := item.Validate(); err != nil {
31+
t.Error(err)
32+
}
33+
34+
tests := adapterhelpers.QueryTests{
35+
{
36+
ExpectedType: "apigateway-rest-api",
37+
ExpectedMethod: sdp.QueryMethod_GET,
38+
ExpectedQuery: "rest-api-id",
39+
ExpectedScope: "scope",
40+
},
41+
}
42+
43+
tests.Execute(t, item)
44+
}
45+
46+
func TestNewAPIGatewayApiKeyAdapter(t *testing.T) {
47+
config, account, region := adapterhelpers.GetAutoConfig(t)
48+
49+
client := apigateway.NewFromConfig(config)
50+
51+
adapter := NewAPIGatewayApiKeyAdapter(client, account, region)
52+
53+
test := adapterhelpers.E2ETest{
54+
Adapter: adapter,
55+
Timeout: 10 * time.Second,
56+
SkipList: true,
57+
}
58+
59+
test.Run(t)
60+
}

adapters/integration/apigateway/apigateway_test.go

Lines changed: 77 additions & 2 deletions
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 itgr test")
30+
t.Log("Running APIGateway integration test")
3131

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

@@ -61,7 +61,14 @@ func APIGateway(t *testing.T) {
6161

6262
err = integrationSource.Validate()
6363
if err != nil {
64-
t.Fatalf("failed to validate APIGateway itgr adapter: %v", err)
64+
t.Fatalf("failed to validate APIGateway integration adapter: %v", err)
65+
}
66+
67+
apiKeySource := adapters.NewAPIGatewayApiKeyAdapter(testClient, accountID, testAWSConfig.Region)
68+
69+
err = apiKeySource.Validate()
70+
if err != nil {
71+
t.Fatalf("failed to validate APIGateway API key adapter: %v", err)
6572
}
6673

6774
scope := adapterhelpers.FormatScope(accountID, testAWSConfig.Region)
@@ -226,5 +233,73 @@ func APIGateway(t *testing.T) {
226233
t.Fatalf("expected integration ID %s, got %s", integrationID, uniqueIntegrationAttr)
227234
}
228235

236+
// List API keys
237+
apiKeys, err := apiKeySource.List(ctx, scope, true)
238+
if err != nil {
239+
t.Fatalf("failed to list APIGateway API keys: %v", err)
240+
}
241+
242+
if len(apiKeys) == 0 {
243+
t.Fatalf("no API keys found")
244+
}
245+
246+
apiKeyUniqueAttribute := apiKeys[0].GetUniqueAttribute()
247+
248+
apiKeyID, err := integration.GetUniqueAttributeValueByTags(
249+
apiKeyUniqueAttribute,
250+
apiKeys,
251+
integration.ResourceTags(integration.APIGateway, apiKeySrc),
252+
true,
253+
)
254+
if err != nil {
255+
t.Fatalf("failed to get API key ID: %v", err)
256+
}
257+
258+
// Get API key
259+
apiKey, err := apiKeySource.Get(ctx, scope, apiKeyID, true)
260+
if err != nil {
261+
t.Fatalf("failed to get APIGateway API key: %v", err)
262+
}
263+
264+
apiKeyIDFromGet, err := integration.GetUniqueAttributeValueByTags(
265+
apiKeyUniqueAttribute,
266+
[]*sdp.Item{apiKey},
267+
integration.ResourceTags(integration.APIGateway, apiKeySrc),
268+
true,
269+
)
270+
if err != nil {
271+
t.Fatalf("failed to get API key ID from get: %v", err)
272+
}
273+
274+
if apiKeyID != apiKeyIDFromGet {
275+
t.Fatalf("expected API key ID %s, got %s", apiKeyID, apiKeyIDFromGet)
276+
}
277+
278+
// Search API keys
279+
apiKeyName := integration.ResourceName(integration.APIGateway, apiKeySrc, integration.TestID())
280+
apiKeysFromSearch, err := apiKeySource.Search(ctx, scope, apiKeyName, true)
281+
if err != nil {
282+
t.Fatalf("failed to search APIGateway API keys: %v", err)
283+
}
284+
285+
if len(apiKeysFromSearch) == 0 {
286+
t.Fatalf("no API keys found")
287+
}
288+
289+
apiKeyIDFromSearch, err := integration.GetUniqueAttributeValueBySignificantAttribute(
290+
apiKeyUniqueAttribute,
291+
"Name",
292+
apiKeyName,
293+
apiKeysFromSearch,
294+
true,
295+
)
296+
if err != nil {
297+
t.Fatalf("failed to get API key ID from search: %v", err)
298+
}
299+
300+
if apiKeyID != apiKeyIDFromSearch {
301+
t.Fatalf("expected API key ID %s, got %s", apiKeyID, apiKeyIDFromSearch)
302+
}
303+
229304
t.Log("APIGateway integration test completed")
230305
}

adapters/integration/apigateway/create.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,3 +168,31 @@ func createIntegration(ctx context.Context, logger *slog.Logger, client *apigate
168168

169169
return nil
170170
}
171+
172+
func createAPIKey(ctx context.Context, logger *slog.Logger, client *apigateway.Client, testID string) error {
173+
// check if an API key with the same name already exists
174+
id, err := findAPIKeyByName(ctx, client, integration.ResourceName(integration.APIGateway, apiKeySrc, testID))
175+
if err != nil {
176+
if errors.As(err, new(integration.NotFoundError)) {
177+
logger.InfoContext(ctx, "Creating API key")
178+
} else {
179+
return err
180+
}
181+
}
182+
183+
if id != nil {
184+
logger.InfoContext(ctx, "API key already exists")
185+
return nil
186+
}
187+
188+
_, err = client.CreateApiKey(ctx, &apigateway.CreateApiKeyInput{
189+
Name: adapterhelpers.PtrString(integration.ResourceName(integration.APIGateway, apiKeySrc, testID)),
190+
Tags: resourceTags(apiKeySrc, testID),
191+
Enabled: true,
192+
})
193+
if err != nil {
194+
return err
195+
}
196+
197+
return nil
198+
}

adapters/integration/apigateway/delete.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package apigateway
22

33
import (
44
"context"
5-
65
"github.com/aws/aws-sdk-go-v2/service/apigateway"
76
"github.com/overmindtech/aws-source/adapterhelpers"
87
)
@@ -14,3 +13,14 @@ func deleteRestAPI(ctx context.Context, client *apigateway.Client, restAPIID str
1413

1514
return err
1615
}
16+
17+
func deleteAPIKeyByName(ctx context.Context, client *apigateway.Client, id *string) error {
18+
_, err := client.DeleteApiKey(ctx, &apigateway.DeleteApiKeyInput{
19+
ApiKey: id,
20+
})
21+
if err != nil {
22+
return err
23+
}
24+
25+
return nil
26+
}

adapters/integration/apigateway/find.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,24 @@ func findIntegration(ctx context.Context, client *apigateway.Client, restAPIID,
110110

111111
return nil
112112
}
113+
114+
func findAPIKeyByName(ctx context.Context, client *apigateway.Client, name string) (*string, error) {
115+
result, err := client.GetApiKeys(ctx, &apigateway.GetApiKeysInput{
116+
NameQuery: &name,
117+
})
118+
if err != nil {
119+
return nil, err
120+
}
121+
122+
if len(result.Items) == 0 {
123+
return nil, integration.NewNotFoundError(integration.ResourceName(integration.APIGateway, apiKeySrc, name))
124+
}
125+
126+
for _, apiKey := range result.Items {
127+
if *apiKey.Name == name {
128+
return apiKey.Id, nil
129+
}
130+
}
131+
132+
return nil, integration.NewNotFoundError(integration.ResourceName(integration.APIGateway, apiKeySrc, name))
133+
}

adapters/integration/apigateway/setup.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const (
1414
methodSrc = "method"
1515
methodResponseSrc = "method-response"
1616
integrationSrc = "integration"
17+
apiKeySrc = "api-key"
1718
)
1819

1920
func setup(ctx context.Context, logger *slog.Logger, client *apigateway.Client) error {
@@ -55,5 +56,11 @@ func setup(ctx context.Context, logger *slog.Logger, client *apigateway.Client)
5556
return err
5657
}
5758

59+
// Create API Key
60+
err = createAPIKey(ctx, logger, client, testID)
61+
if err != nil {
62+
return err
63+
}
64+
5865
return nil
5966
}

0 commit comments

Comments
 (0)