From 72b6e09ae6fa25af92350a8e5685446e29b2ed2a Mon Sep 17 00:00:00 2001 From: tomas Date: Tue, 7 Jan 2025 22:57:32 +0000 Subject: [PATCH 01/12] add apigateway integration adapter --- adapters/apigateway-integration.go | 125 ++++++++++++++++++ adapters/apigateway-integration_test.go | 90 +++++++++++++ .../integration/apigateway/apigateway_test.go | 25 +++- adapters/integration/apigateway/create.go | 29 ++++ adapters/integration/apigateway/find.go | 23 ++++ adapters/integration/apigateway/setup.go | 7 + proc/proc.go | 1 + 7 files changed, 299 insertions(+), 1 deletion(-) create mode 100644 adapters/apigateway-integration.go create mode 100644 adapters/apigateway-integration_test.go diff --git a/adapters/apigateway-integration.go b/adapters/apigateway-integration.go new file mode 100644 index 00000000..2b72e8c8 --- /dev/null +++ b/adapters/apigateway-integration.go @@ -0,0 +1,125 @@ +package adapters + +import ( + "context" + "fmt" + "log/slog" + "strings" + + "github.com/aws/aws-sdk-go-v2/service/apigateway" + "github.com/overmindtech/aws-source/adapterhelpers" + "github.com/overmindtech/sdp-go" +) + +type apiGatewayIntegrationGetter interface { + GetIntegration(ctx context.Context, params *apigateway.GetIntegrationInput, optFns ...func(*apigateway.Options)) (*apigateway.GetIntegrationOutput, error) +} + +func apiGatewayIntegrationGetFunc(ctx context.Context, client apiGatewayIntegrationGetter, scope string, input *apigateway.GetIntegrationInput) (*sdp.Item, error) { + output, err := client.GetIntegration(ctx, input) + if err != nil { + return nil, err + } + + attributes, err := adapterhelpers.ToAttributesWithExclude(output, "tags") + if err != nil { + return nil, err + } + + // We create a custom ID of {rest-api-id}/{resource-id}/{http-method} e.g. + // rest-api-id/resource-id/GET + integrationID := fmt.Sprintf( + "%s/%s/%s", + *input.RestApiId, + *input.ResourceId, + *input.HttpMethod, + ) + err = attributes.Set("IntegrationID", integrationID) + if err != nil { + return nil, err + } + + item := &sdp.Item{ + Type: "apigateway-integration", + UniqueAttribute: "IntegrationID", + Attributes: attributes, + Scope: scope, + } + + item.LinkedItemQueries = append(item.LinkedItemQueries, &sdp.LinkedItemQuery{ + Query: &sdp.Query{ + Type: "apigateway-method", + Method: sdp.QueryMethod_GET, + Query: fmt.Sprintf("%s/%s/%s", *input.RestApiId, *input.ResourceId, *input.HttpMethod), + Scope: scope, + }, + BlastPropagation: &sdp.BlastPropagation{ + // They are tightly coupled + In: true, + Out: true, + }, + }) + + if output.ConnectionId != nil { + item.LinkedItemQueries = append(item.LinkedItemQueries, &sdp.LinkedItemQuery{ + Query: &sdp.Query{ + Type: "apigateway-vpc-link", + Method: sdp.QueryMethod_GET, + Query: *output.ConnectionId, + Scope: scope, + }, + BlastPropagation: &sdp.BlastPropagation{ + // If VPC link goes away, so does the integration + In: true, + // If integration goes away, VPC link is still there + Out: false, + }, + }) + } + + return item, nil +} + +func NewAPIGatewayIntegrationAdapter(client apiGatewayIntegrationGetter, accountID string, region string) *adapterhelpers.AlwaysGetAdapter[*apigateway.GetIntegrationInput, *apigateway.GetIntegrationOutput, *apigateway.GetIntegrationInput, *apigateway.GetIntegrationOutput, apiGatewayIntegrationGetter, *apigateway.Options] { + return &adapterhelpers.AlwaysGetAdapter[*apigateway.GetIntegrationInput, *apigateway.GetIntegrationOutput, *apigateway.GetIntegrationInput, *apigateway.GetIntegrationOutput, apiGatewayIntegrationGetter, *apigateway.Options]{ + ItemType: "apigateway-integration", + Client: client, + AccountID: accountID, + Region: region, + AdapterMetadata: apiGatewayIntegrationAdapterMetadata, + GetFunc: apiGatewayIntegrationGetFunc, + GetInputMapper: func(scope, query string) *apigateway.GetIntegrationInput { + // We are using a custom id of {rest-api-id}/{resource-id}/{http-method} e.g. + // rest-api-id/resource-id/GET + f := strings.Split(query, "/") + if len(f) != 3 { + slog.Error( + "query must be in the format of: rest-api-id/resource-id/http-method", + "found", + query, + ) + + return nil + } + + return &apigateway.GetIntegrationInput{ + RestApiId: &f[0], + ResourceId: &f[1], + HttpMethod: &f[2], + } + }, + DisableList: true, + } +} + +var apiGatewayIntegrationAdapterMetadata = Metadata.Register(&sdp.AdapterMetadata{ + Type: "apigateway-integration", + DescriptiveName: "API Gateway Integration", + Category: sdp.AdapterCategory_ADAPTER_CATEGORY_NETWORK, + SupportedQueryMethods: &sdp.AdapterSupportedQueryMethods{ + Get: true, + GetDescription: "Get an Integration by rest-api id, resource id, and http-method", + Search: true, + SearchDescription: "Search Integrations by ARN", + }, +}) diff --git a/adapters/apigateway-integration_test.go b/adapters/apigateway-integration_test.go new file mode 100644 index 00000000..2d012182 --- /dev/null +++ b/adapters/apigateway-integration_test.go @@ -0,0 +1,90 @@ +package adapters + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/apigateway" + "github.com/aws/aws-sdk-go-v2/service/apigateway/types" + "github.com/overmindtech/aws-source/adapterhelpers" + "github.com/overmindtech/sdp-go" +) + +type mockAPIGatewayIntegrationClient struct{} + +func (m *mockAPIGatewayIntegrationClient) GetIntegration(ctx context.Context, params *apigateway.GetIntegrationInput, optFns ...func(*apigateway.Options)) (*apigateway.GetIntegrationOutput, error) { + return &apigateway.GetIntegrationOutput{ + IntegrationResponses: map[string]types.IntegrationResponse{ + "200": { + ResponseTemplates: map[string]string{ + "application/json": "", + }, + StatusCode: aws.String("200"), + }, + }, + CacheKeyParameters: []string{}, + 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"), + HttpMethod: aws.String("POST"), + CacheNamespace: aws.String("y9h6rt"), + Type: "AWS", + ConnectionId: aws.String("vpc-connection-id"), + }, nil +} + +func TestApiGatewayIntegrationGetFunc(t *testing.T) { + ctx := context.Background() + cli := mockAPIGatewayIntegrationClient{} + + input := &apigateway.GetIntegrationInput{ + RestApiId: aws.String("rest-api-id"), + ResourceId: aws.String("resource-id"), + HttpMethod: aws.String("GET"), + } + + item, err := apiGatewayIntegrationGetFunc(ctx, &cli, "scope", input) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if err = item.Validate(); err != nil { + t.Fatal(err) + } + + integrationID := fmt.Sprintf("%s/%s/%s", *input.RestApiId, *input.ResourceId, *input.HttpMethod) + + tests := adapterhelpers.QueryTests{ + { + ExpectedType: "apigateway-method", + ExpectedMethod: sdp.QueryMethod_GET, + ExpectedQuery: integrationID, + ExpectedScope: "scope", + }, + { + ExpectedType: "apigateway-vpc-link", + ExpectedMethod: sdp.QueryMethod_GET, + ExpectedQuery: "vpc-connection-id", + ExpectedScope: "scope", + }, + } + + tests.Execute(t, item) +} + +func TestNewAPIGatewayIntegrationAdapter(t *testing.T) { + config, account, region := adapterhelpers.GetAutoConfig(t) + + client := apigateway.NewFromConfig(config) + + adapter := NewAPIGatewayIntegrationAdapter(client, account, region) + + test := adapterhelpers.E2ETest{ + Adapter: adapter, + Timeout: 10 * time.Second, + SkipList: true, + } + + test.Run(t) +} diff --git a/adapters/integration/apigateway/apigateway_test.go b/adapters/integration/apigateway/apigateway_test.go index f547848b..f9825de9 100644 --- a/adapters/integration/apigateway/apigateway_test.go +++ b/adapters/integration/apigateway/apigateway_test.go @@ -27,7 +27,7 @@ func APIGateway(t *testing.T) { accountID := testAWSConfig.AccountID - t.Log("Running APIGateway integration test") + t.Log("Running APIGateway itgr test") restApiSource := adapters.NewAPIGatewayRestApiAdapter(testClient, accountID, testAWSConfig.Region) @@ -57,6 +57,13 @@ func APIGateway(t *testing.T) { t.Fatalf("failed to validate APIGateway method response adapter: %v", err) } + integrationSource := adapters.NewAPIGatewayIntegrationAdapter(testClient, accountID, testAWSConfig.Region) + + err = integrationSource.Validate() + if err != nil { + t.Fatalf("failed to validate APIGateway itgr adapter: %v", err) + } + scope := adapterhelpers.FormatScope(accountID, testAWSConfig.Region) // List restApis @@ -203,5 +210,21 @@ func APIGateway(t *testing.T) { t.Fatalf("expected method response ID %s, got %s", methodResponseID, uniqueMethodResponseAttr) } + // Get integration + integrationID := fmt.Sprintf("%s/GET", resourceUniqueAttrFromGet) // resourceUniqueAttribute contains the restApiID + itgr, err := integrationSource.Get(ctx, scope, integrationID, true) + if err != nil { + t.Fatalf("failed to get APIGateway itgr: %v", err) + } + + uniqueIntegrationAttr, err := itgr.GetAttributes().Get(itgr.GetUniqueAttribute()) + if err != nil { + t.Fatalf("failed to get unique itgr attribute: %v", err) + } + + if uniqueIntegrationAttr != integrationID { + t.Fatalf("expected integration ID %s, got %s", integrationID, uniqueIntegrationAttr) + } + t.Log("APIGateway integration test completed") } diff --git a/adapters/integration/apigateway/create.go b/adapters/integration/apigateway/create.go index 94517051..7deb7d2e 100644 --- a/adapters/integration/apigateway/create.go +++ b/adapters/integration/apigateway/create.go @@ -139,3 +139,32 @@ func createMethodResponse(ctx context.Context, logger *slog.Logger, client *apig return nil } + +func createIntegration(ctx context.Context, logger *slog.Logger, client *apigateway.Client, restAPIID, resourceID *string, method string) error { + // check if an integration with the same method already exists + err := findIntegration(ctx, client, restAPIID, resourceID, method) + if err != nil { + if errors.As(err, new(integration.NotFoundError)) { + logger.InfoContext(ctx, "Creating integration") + } else { + return err + } + } + + if err == nil { + logger.InfoContext(ctx, "Integration already exists") + return nil + } + + _, err = client.PutIntegration(ctx, &apigateway.PutIntegrationInput{ + RestApiId: restAPIID, + ResourceId: resourceID, + HttpMethod: adapterhelpers.PtrString(method), + Type: "MOCK", + }) + if err != nil { + return err + } + + return nil +} diff --git a/adapters/integration/apigateway/find.go b/adapters/integration/apigateway/find.go index cf146509..d6c507d8 100644 --- a/adapters/integration/apigateway/find.go +++ b/adapters/integration/apigateway/find.go @@ -87,3 +87,26 @@ func findMethodResponse(ctx context.Context, client *apigateway.Client, restAPII return nil } + +func findIntegration(ctx context.Context, client *apigateway.Client, restAPIID, resourceID *string, method string) error { + _, err := client.GetIntegration(ctx, &apigateway.GetIntegrationInput{ + RestApiId: restAPIID, + ResourceId: resourceID, + HttpMethod: &method, + }) + + if err != nil { + var notFoundErr *types.NotFoundException + if errors.As(err, ¬FoundErr) { + return integration.NewNotFoundError(integration.ResourceName( + integration.APIGateway, + integrationSrc, + method, + )) + } + + return err + } + + return nil +} diff --git a/adapters/integration/apigateway/setup.go b/adapters/integration/apigateway/setup.go index 46f29b6f..f1b4996f 100644 --- a/adapters/integration/apigateway/setup.go +++ b/adapters/integration/apigateway/setup.go @@ -13,6 +13,7 @@ const ( resourceSrc = "resource" methodSrc = "method" methodResponseSrc = "method-response" + integrationSrc = "integration" ) 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) return err } + // Create integration + err = createIntegration(ctx, logger, client, restApiID, testResourceID, "GET") + if err != nil { + return err + } + return nil } diff --git a/proc/proc.go b/proc/proc.go index b1b1dec1..2fcef46d 100644 --- a/proc/proc.go +++ b/proc/proc.go @@ -481,6 +481,7 @@ func InitializeAwsSourceEngine(ctx context.Context, ec *discovery.EngineConfig, adapters.NewAPIGatewayDomainNameAdapter(apigatewayClient, *callerID.Account, cfg.Region), adapters.NewAPIGatewayMethodAdapter(apigatewayClient, *callerID.Account, cfg.Region), adapters.NewAPIGatewayMethodResponseAdapter(apigatewayClient, *callerID.Account, cfg.Region), + adapters.NewAPIGatewayIntegrationAdapter(apigatewayClient, *callerID.Account, cfg.Region), // SSM adapters.NewSSMParameterAdapter(ssmClient, *callerID.Account, cfg.Region), From 007c6f7388300def8d73857ce5ca69a440145e93 Mon Sep 17 00:00:00 2001 From: tomas Date: Wed, 8 Jan 2025 22:31:14 +0000 Subject: [PATCH 02/12] add apigateway vpc link adapter --- adapters/apigateway-vpc-link.go | 135 +++++++++++++++++++++++++++ adapters/apigateway-vpc-link_test.go | 58 ++++++++++++ proc/proc.go | 1 + 3 files changed, 194 insertions(+) create mode 100644 adapters/apigateway-vpc-link.go create mode 100644 adapters/apigateway-vpc-link_test.go diff --git a/adapters/apigateway-vpc-link.go b/adapters/apigateway-vpc-link.go new file mode 100644 index 00000000..9425fa77 --- /dev/null +++ b/adapters/apigateway-vpc-link.go @@ -0,0 +1,135 @@ +package adapters + +import ( + "context" + "strings" + + "github.com/aws/aws-sdk-go-v2/service/apigateway" + "github.com/aws/aws-sdk-go-v2/service/apigateway/types" + "github.com/overmindtech/aws-source/adapterhelpers" + "github.com/overmindtech/sdp-go" +) + +// convertGetVpcLinkOutputToVpcLink converts a GetVpcLinkOutput to a VpcLink +func convertGetVpcLinkOutputToVpcLink(output *apigateway.GetVpcLinkOutput) *types.VpcLink { + return &types.VpcLink{ + Id: output.Id, + Name: output.Name, + Description: output.Description, + TargetArns: output.TargetArns, + Status: output.Status, + Tags: output.Tags, + } +} + +func vpcLinkListFunc(ctx context.Context, client *apigateway.Client, _ string) ([]*types.VpcLink, error) { + out, err := client.GetVpcLinks(ctx, &apigateway.GetVpcLinksInput{}) + if err != nil { + return nil, err + } + + var items []*types.VpcLink + for _, vpcLink := range out.Items { + items = append(items, &vpcLink) + } + + return items, nil +} + +func vpcLinkOutputMapper(scope string, awsItem *types.VpcLink) (*sdp.Item, error) { + attributes, err := adapterhelpers.ToAttributesWithExclude(awsItem, "tags") + if err != nil { + return nil, err + } + + item := sdp.Item{ + Type: "apigateway-vpc-link", + UniqueAttribute: "Id", + Attributes: attributes, + Scope: scope, + Tags: awsItem.Tags, + } + + // The status of the VPC link. The valid values are AVAILABLE , PENDING , DELETING , or FAILED. + switch awsItem.Status { + case types.VpcLinkStatusAvailable: + item.Health = sdp.Health_HEALTH_OK.Enum() + case types.VpcLinkStatusPending: + item.Health = sdp.Health_HEALTH_PENDING.Enum() + case types.VpcLinkStatusDeleting: + item.Health = sdp.Health_HEALTH_PENDING.Enum() + case types.VpcLinkStatusFailed: + item.Health = sdp.Health_HEALTH_ERROR.Enum() + } + + for _, targetArn := range awsItem.TargetArns { + item.LinkedItemQueries = append(item.LinkedItemQueries, &sdp.LinkedItemQuery{ + Query: &sdp.Query{ + Type: "elbv2-load-balancer", + Method: sdp.QueryMethod_SEARCH, + Query: targetArn, + Scope: scope, + }, + BlastPropagation: &sdp.BlastPropagation{ + // Any change on the load balancer will affect the VPC link + In: true, + // Any change on the VPC link won't affect the load balancer + Out: false, + }, + }) + } + + return &item, nil +} + +func NewAPIGatewayVpcLinkAdapter(client *apigateway.Client, accountID string, region string) *adapterhelpers.GetListAdapter[*types.VpcLink, *apigateway.Client, *apigateway.Options] { + return &adapterhelpers.GetListAdapter[*types.VpcLink, *apigateway.Client, *apigateway.Options]{ + ItemType: "apigateway-vpc-link", + Client: client, + AccountID: accountID, + Region: region, + AdapterMetadata: vpcLinkAdapterMetadata, + GetFunc: func(ctx context.Context, client *apigateway.Client, scope, query string) (*types.VpcLink, error) { + out, err := client.GetVpcLink(ctx, &apigateway.GetVpcLinkInput{ + VpcLinkId: &query, + }) + if err != nil { + return nil, err + } + return convertGetVpcLinkOutputToVpcLink(out), nil + }, + ListFunc: vpcLinkListFunc, + SearchFunc: func(ctx context.Context, client *apigateway.Client, scope string, query string) ([]*types.VpcLink, error) { + out, err := client.GetVpcLinks(ctx, &apigateway.GetVpcLinksInput{}) + if err != nil { + return nil, err + } + + var items []*types.VpcLink + for _, vpcLink := range out.Items { + if strings.Contains(*vpcLink.Name, query) { + items = append(items, &vpcLink) + } + } + + return items, nil + }, + ItemMapper: func(_, scope string, awsItem *types.VpcLink) (*sdp.Item, error) { + return vpcLinkOutputMapper(scope, awsItem) + }, + } +} + +var vpcLinkAdapterMetadata = Metadata.Register(&sdp.AdapterMetadata{ + Type: "apigateway-vpc-link", + DescriptiveName: "VPC Link", + Category: sdp.AdapterCategory_ADAPTER_CATEGORY_NETWORK, + SupportedQueryMethods: &sdp.AdapterSupportedQueryMethods{ + Get: true, + List: true, + Search: true, + GetDescription: "Get a VPC Link by ID", + ListDescription: "List all VPC Links", + SearchDescription: "Search for VPC Links by their name", + }, +}) diff --git a/adapters/apigateway-vpc-link_test.go b/adapters/apigateway-vpc-link_test.go new file mode 100644 index 00000000..94f6327a --- /dev/null +++ b/adapters/apigateway-vpc-link_test.go @@ -0,0 +1,58 @@ +package adapters + +import ( + "testing" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/apigateway" + "github.com/aws/aws-sdk-go-v2/service/apigateway/types" + "github.com/overmindtech/aws-source/adapterhelpers" + "github.com/overmindtech/sdp-go" +) + +func TestVpcLinkOutputMapper(t *testing.T) { + awsItem := &types.VpcLink{ + Id: aws.String("vpc-link-id"), + Name: aws.String("vpc-link-name"), + Description: aws.String("vpc-link-description"), + TargetArns: []string{"arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188"}, + Status: types.VpcLinkStatusAvailable, + Tags: map[string]string{"key": "value"}, + } + + item, err := vpcLinkOutputMapper("scope", awsItem) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if err := item.Validate(); err != nil { + t.Error(err) + } + + tests := adapterhelpers.QueryTests{ + { + ExpectedType: "elbv2-load-balancer", + ExpectedMethod: sdp.QueryMethod_SEARCH, + ExpectedQuery: "arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188", + ExpectedScope: "scope", + }, + } + + tests.Execute(t, item) +} + +func TestNewAPIGatewayVpcLinkAdapter(t *testing.T) { + config, account, region := adapterhelpers.GetAutoConfig(t) + + client := apigateway.NewFromConfig(config) + + adapter := NewAPIGatewayVpcLinkAdapter(client, account, region) + + test := adapterhelpers.E2ETest{ + Adapter: adapter, + Timeout: 10 * time.Second, + } + + test.Run(t) +} diff --git a/proc/proc.go b/proc/proc.go index 2fcef46d..95cde527 100644 --- a/proc/proc.go +++ b/proc/proc.go @@ -482,6 +482,7 @@ func InitializeAwsSourceEngine(ctx context.Context, ec *discovery.EngineConfig, adapters.NewAPIGatewayMethodAdapter(apigatewayClient, *callerID.Account, cfg.Region), adapters.NewAPIGatewayMethodResponseAdapter(apigatewayClient, *callerID.Account, cfg.Region), adapters.NewAPIGatewayIntegrationAdapter(apigatewayClient, *callerID.Account, cfg.Region), + adapters.NewAPIGatewayVpcLinkAdapter(apigatewayClient, *callerID.Account, cfg.Region), // SSM adapters.NewSSMParameterAdapter(ssmClient, *callerID.Account, cfg.Region), From 85b87419fc9fc170041906ad27a19c9dffa54560 Mon Sep 17 00:00:00 2001 From: tomas Date: Fri, 10 Jan 2025 23:32:54 +0000 Subject: [PATCH 03/12] add apigateway api key adapter --- adapters/apigateway-api-key.go | 126 ++++++++++++++++++ adapters/apigateway-api-key_test.go | 60 +++++++++ .../integration/apigateway/apigateway_test.go | 79 ++++++++++- adapters/integration/apigateway/create.go | 28 ++++ adapters/integration/apigateway/delete.go | 12 +- adapters/integration/apigateway/find.go | 21 +++ adapters/integration/apigateway/setup.go | 7 + adapters/integration/apigateway/teardown.go | 22 ++- proc/proc.go | 1 + tracing/commit.txt | 1 + 10 files changed, 353 insertions(+), 4 deletions(-) create mode 100644 adapters/apigateway-api-key.go create mode 100644 adapters/apigateway-api-key_test.go create mode 100644 tracing/commit.txt diff --git a/adapters/apigateway-api-key.go b/adapters/apigateway-api-key.go new file mode 100644 index 00000000..9519fcaf --- /dev/null +++ b/adapters/apigateway-api-key.go @@ -0,0 +1,126 @@ +package adapters + +import ( + "context" + "github.com/aws/aws-sdk-go-v2/service/apigateway" + "github.com/aws/aws-sdk-go-v2/service/apigateway/types" + "github.com/overmindtech/aws-source/adapterhelpers" + "github.com/overmindtech/sdp-go" + "strings" +) + +// convertGetApiKeyOutputToApiKey converts a GetApiKeyOutput to an ApiKey +func convertGetApiKeyOutputToApiKey(output *apigateway.GetApiKeyOutput) *types.ApiKey { + return &types.ApiKey{ + Id: output.Id, + Name: output.Name, + Enabled: output.Enabled, + CreatedDate: output.CreatedDate, + LastUpdatedDate: output.LastUpdatedDate, + StageKeys: output.StageKeys, + Tags: output.Tags, + } +} + +func apiKeyListFunc(ctx context.Context, client *apigateway.Client, _ string) ([]*types.ApiKey, error) { + out, err := client.GetApiKeys(ctx, &apigateway.GetApiKeysInput{}) + if err != nil { + return nil, err + } + + var items []*types.ApiKey + for _, apiKey := range out.Items { + items = append(items, &apiKey) + } + + return items, nil +} + +func apiKeyOutputMapper(scope string, awsItem *types.ApiKey) (*sdp.Item, error) { + attributes, err := adapterhelpers.ToAttributesWithExclude(awsItem, "tags") + if err != nil { + return nil, err + } + + item := sdp.Item{ + Type: "apigateway-api-key", + UniqueAttribute: "Id", + Attributes: attributes, + Scope: scope, + Tags: awsItem.Tags, + } + + for _, key := range awsItem.StageKeys { + // {restApiId}/{stage} + restAPIID := strings.Split(key, "/")[0] + if restAPIID != "" { + item.LinkedItemQueries = append(item.LinkedItemQueries, &sdp.LinkedItemQuery{ + Query: &sdp.Query{ + Type: "apigateway-rest-api", + Method: sdp.QueryMethod_GET, + Query: restAPIID, + Scope: scope, + }, + BlastPropagation: &sdp.BlastPropagation{ + // They are tightly coupled, so we need to propagate both ways + In: true, + Out: true, + }, + }) + } + } + + return &item, nil +} + +func NewAPIGatewayApiKeyAdapter(client *apigateway.Client, accountID string, region string) *adapterhelpers.GetListAdapter[*types.ApiKey, *apigateway.Client, *apigateway.Options] { + return &adapterhelpers.GetListAdapter[*types.ApiKey, *apigateway.Client, *apigateway.Options]{ + ItemType: "apigateway-api-key", + Client: client, + AccountID: accountID, + Region: region, + AdapterMetadata: apiKeyAdapterMetadata, + GetFunc: func(ctx context.Context, client *apigateway.Client, scope, query string) (*types.ApiKey, error) { + out, err := client.GetApiKey(ctx, &apigateway.GetApiKeyInput{ + ApiKey: &query, + }) + if err != nil { + return nil, err + } + return convertGetApiKeyOutputToApiKey(out), nil + }, + ListFunc: apiKeyListFunc, + SearchFunc: func(ctx context.Context, client *apigateway.Client, scope string, query string) ([]*types.ApiKey, error) { + out, err := client.GetApiKeys(ctx, &apigateway.GetApiKeysInput{ + NameQuery: &query, + }) + if err != nil { + return nil, err + } + + var items []*types.ApiKey + for _, apiKey := range out.Items { + items = append(items, &apiKey) + } + + return items, nil + }, + ItemMapper: func(_, scope string, awsItem *types.ApiKey) (*sdp.Item, error) { + return apiKeyOutputMapper(scope, awsItem) + }, + } +} + +var apiKeyAdapterMetadata = Metadata.Register(&sdp.AdapterMetadata{ + Type: "apigateway-api-key", + DescriptiveName: "API Key", + Category: sdp.AdapterCategory_ADAPTER_CATEGORY_SECURITY, + SupportedQueryMethods: &sdp.AdapterSupportedQueryMethods{ + Get: true, + List: true, + Search: true, + GetDescription: "Get an API Key by ID", + ListDescription: "List all API Keys", + SearchDescription: "Search for API Keys by their name", + }, +}) diff --git a/adapters/apigateway-api-key_test.go b/adapters/apigateway-api-key_test.go new file mode 100644 index 00000000..3380323b --- /dev/null +++ b/adapters/apigateway-api-key_test.go @@ -0,0 +1,60 @@ +package adapters + +import ( + "testing" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/apigateway" + "github.com/aws/aws-sdk-go-v2/service/apigateway/types" + "github.com/overmindtech/aws-source/adapterhelpers" + "github.com/overmindtech/sdp-go" +) + +func TestApiKeyOutputMapper(t *testing.T) { + awsItem := &types.ApiKey{ + Id: aws.String("api-key-id"), + Name: aws.String("api-key-name"), + Enabled: true, + CreatedDate: aws.Time(time.Now()), + LastUpdatedDate: aws.Time(time.Now()), + StageKeys: []string{"rest-api-id/stage"}, + Tags: map[string]string{"key": "value"}, + } + + item, err := apiKeyOutputMapper("scope", awsItem) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if err := item.Validate(); err != nil { + t.Error(err) + } + + tests := adapterhelpers.QueryTests{ + { + ExpectedType: "apigateway-rest-api", + ExpectedMethod: sdp.QueryMethod_GET, + ExpectedQuery: "rest-api-id", + ExpectedScope: "scope", + }, + } + + tests.Execute(t, item) +} + +func TestNewAPIGatewayApiKeyAdapter(t *testing.T) { + config, account, region := adapterhelpers.GetAutoConfig(t) + + client := apigateway.NewFromConfig(config) + + adapter := NewAPIGatewayApiKeyAdapter(client, account, region) + + test := adapterhelpers.E2ETest{ + Adapter: adapter, + Timeout: 10 * time.Second, + SkipList: true, + } + + test.Run(t) +} diff --git a/adapters/integration/apigateway/apigateway_test.go b/adapters/integration/apigateway/apigateway_test.go index f9825de9..4c724180 100644 --- a/adapters/integration/apigateway/apigateway_test.go +++ b/adapters/integration/apigateway/apigateway_test.go @@ -27,7 +27,7 @@ func APIGateway(t *testing.T) { accountID := testAWSConfig.AccountID - t.Log("Running APIGateway itgr test") + t.Log("Running APIGateway integration test") restApiSource := adapters.NewAPIGatewayRestApiAdapter(testClient, accountID, testAWSConfig.Region) @@ -61,7 +61,14 @@ func APIGateway(t *testing.T) { err = integrationSource.Validate() if err != nil { - t.Fatalf("failed to validate APIGateway itgr adapter: %v", err) + t.Fatalf("failed to validate APIGateway integration adapter: %v", err) + } + + apiKeySource := adapters.NewAPIGatewayApiKeyAdapter(testClient, accountID, testAWSConfig.Region) + + err = apiKeySource.Validate() + if err != nil { + t.Fatalf("failed to validate APIGateway API key adapter: %v", err) } scope := adapterhelpers.FormatScope(accountID, testAWSConfig.Region) @@ -226,5 +233,73 @@ func APIGateway(t *testing.T) { t.Fatalf("expected integration ID %s, got %s", integrationID, uniqueIntegrationAttr) } + // List API keys + apiKeys, err := apiKeySource.List(ctx, scope, true) + if err != nil { + t.Fatalf("failed to list APIGateway API keys: %v", err) + } + + if len(apiKeys) == 0 { + t.Fatalf("no API keys found") + } + + apiKeyUniqueAttribute := apiKeys[0].GetUniqueAttribute() + + apiKeyID, err := integration.GetUniqueAttributeValueByTags( + apiKeyUniqueAttribute, + apiKeys, + integration.ResourceTags(integration.APIGateway, apiKeySrc), + true, + ) + if err != nil { + t.Fatalf("failed to get API key ID: %v", err) + } + + // Get API key + apiKey, err := apiKeySource.Get(ctx, scope, apiKeyID, true) + if err != nil { + t.Fatalf("failed to get APIGateway API key: %v", err) + } + + apiKeyIDFromGet, err := integration.GetUniqueAttributeValueByTags( + apiKeyUniqueAttribute, + []*sdp.Item{apiKey}, + integration.ResourceTags(integration.APIGateway, apiKeySrc), + true, + ) + if err != nil { + t.Fatalf("failed to get API key ID from get: %v", err) + } + + if apiKeyID != apiKeyIDFromGet { + t.Fatalf("expected API key ID %s, got %s", apiKeyID, apiKeyIDFromGet) + } + + // Search API keys + apiKeyName := integration.ResourceName(integration.APIGateway, apiKeySrc, integration.TestID()) + apiKeysFromSearch, err := apiKeySource.Search(ctx, scope, apiKeyName, true) + if err != nil { + t.Fatalf("failed to search APIGateway API keys: %v", err) + } + + if len(apiKeysFromSearch) == 0 { + t.Fatalf("no API keys found") + } + + apiKeyIDFromSearch, err := integration.GetUniqueAttributeValueBySignificantAttribute( + apiKeyUniqueAttribute, + "Name", + apiKeyName, + apiKeysFromSearch, + true, + ) + if err != nil { + t.Fatalf("failed to get API key ID from search: %v", err) + } + + if apiKeyID != apiKeyIDFromSearch { + t.Fatalf("expected API key ID %s, got %s", apiKeyID, apiKeyIDFromSearch) + } + t.Log("APIGateway integration test completed") } diff --git a/adapters/integration/apigateway/create.go b/adapters/integration/apigateway/create.go index 7deb7d2e..21ab5ddb 100644 --- a/adapters/integration/apigateway/create.go +++ b/adapters/integration/apigateway/create.go @@ -168,3 +168,31 @@ func createIntegration(ctx context.Context, logger *slog.Logger, client *apigate return nil } + +func createAPIKey(ctx context.Context, logger *slog.Logger, client *apigateway.Client, testID string) error { + // check if an API key with the same name already exists + id, err := findAPIKeyByName(ctx, client, integration.ResourceName(integration.APIGateway, apiKeySrc, testID)) + if err != nil { + if errors.As(err, new(integration.NotFoundError)) { + logger.InfoContext(ctx, "Creating API key") + } else { + return err + } + } + + if id != nil { + logger.InfoContext(ctx, "API key already exists") + return nil + } + + _, err = client.CreateApiKey(ctx, &apigateway.CreateApiKeyInput{ + Name: adapterhelpers.PtrString(integration.ResourceName(integration.APIGateway, apiKeySrc, testID)), + Tags: resourceTags(apiKeySrc, testID), + Enabled: true, + }) + if err != nil { + return err + } + + return nil +} diff --git a/adapters/integration/apigateway/delete.go b/adapters/integration/apigateway/delete.go index 16222eb3..e8008585 100644 --- a/adapters/integration/apigateway/delete.go +++ b/adapters/integration/apigateway/delete.go @@ -2,7 +2,6 @@ package apigateway import ( "context" - "github.com/aws/aws-sdk-go-v2/service/apigateway" "github.com/overmindtech/aws-source/adapterhelpers" ) @@ -14,3 +13,14 @@ func deleteRestAPI(ctx context.Context, client *apigateway.Client, restAPIID str return err } + +func deleteAPIKeyByName(ctx context.Context, client *apigateway.Client, id *string) error { + _, err := client.DeleteApiKey(ctx, &apigateway.DeleteApiKeyInput{ + ApiKey: id, + }) + if err != nil { + return err + } + + return nil +} diff --git a/adapters/integration/apigateway/find.go b/adapters/integration/apigateway/find.go index d6c507d8..7d8eb309 100644 --- a/adapters/integration/apigateway/find.go +++ b/adapters/integration/apigateway/find.go @@ -110,3 +110,24 @@ func findIntegration(ctx context.Context, client *apigateway.Client, restAPIID, return nil } + +func findAPIKeyByName(ctx context.Context, client *apigateway.Client, name string) (*string, error) { + result, err := client.GetApiKeys(ctx, &apigateway.GetApiKeysInput{ + NameQuery: &name, + }) + if err != nil { + return nil, err + } + + if len(result.Items) == 0 { + return nil, integration.NewNotFoundError(integration.ResourceName(integration.APIGateway, apiKeySrc, name)) + } + + for _, apiKey := range result.Items { + if *apiKey.Name == name { + return apiKey.Id, nil + } + } + + return nil, integration.NewNotFoundError(integration.ResourceName(integration.APIGateway, apiKeySrc, name)) +} diff --git a/adapters/integration/apigateway/setup.go b/adapters/integration/apigateway/setup.go index f1b4996f..021a2f65 100644 --- a/adapters/integration/apigateway/setup.go +++ b/adapters/integration/apigateway/setup.go @@ -14,6 +14,7 @@ const ( methodSrc = "method" methodResponseSrc = "method-response" integrationSrc = "integration" + apiKeySrc = "api-key" ) 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) return err } + // Create API Key + err = createAPIKey(ctx, logger, client, testID) + if err != nil { + return err + } + return nil } diff --git a/adapters/integration/apigateway/teardown.go b/adapters/integration/apigateway/teardown.go index 247f2f64..30e4be85 100644 --- a/adapters/integration/apigateway/teardown.go +++ b/adapters/integration/apigateway/teardown.go @@ -14,11 +14,31 @@ func teardown(ctx context.Context, logger *slog.Logger, client *apigateway.Clien if err != nil { if nf := integration.NewNotFoundError(restAPISrc); errors.As(err, &nf) { logger.WarnContext(ctx, "Rest API not found") + } else { + return err + } + } else { + err = deleteRestAPI(ctx, client, *restAPIID) + if err != nil { + return err + } + } + + keyName := integration.ResourceName(integration.APIGateway, apiKeySrc, integration.TestID()) + apiKeyID, err := findAPIKeyByName(ctx, client, keyName) + if err != nil { + if nf := integration.NewNotFoundError(apiKeySrc); errors.As(err, &nf) { + logger.WarnContext(ctx, "API Key not found", "name", keyName) return nil } else { return err } + } else { + err = deleteAPIKeyByName(ctx, client, apiKeyID) + if err != nil { + return err + } } - return deleteRestAPI(ctx, client, *restAPIID) + return nil } diff --git a/proc/proc.go b/proc/proc.go index 95cde527..00885f09 100644 --- a/proc/proc.go +++ b/proc/proc.go @@ -483,6 +483,7 @@ func InitializeAwsSourceEngine(ctx context.Context, ec *discovery.EngineConfig, adapters.NewAPIGatewayMethodResponseAdapter(apigatewayClient, *callerID.Account, cfg.Region), adapters.NewAPIGatewayIntegrationAdapter(apigatewayClient, *callerID.Account, cfg.Region), adapters.NewAPIGatewayVpcLinkAdapter(apigatewayClient, *callerID.Account, cfg.Region), + adapters.NewAPIGatewayApiKeyAdapter(apigatewayClient, *callerID.Account, cfg.Region), // SSM adapters.NewSSMParameterAdapter(ssmClient, *callerID.Account, cfg.Region), diff --git a/tracing/commit.txt b/tracing/commit.txt new file mode 100644 index 00000000..c8ca9b98 --- /dev/null +++ b/tracing/commit.txt @@ -0,0 +1 @@ +6dad160 \ No newline at end of file From c65e2925a10fa09ba7f3b428e3f66b67df242ec6 Mon Sep 17 00:00:00 2001 From: tomas Date: Mon, 13 Jan 2025 23:25:44 +0000 Subject: [PATCH 04/12] add apigateway authorizer adapter --- adapters/apigateway-authorizer.go | 126 ++++++++++++++++++ adapters/apigateway-authorizer_test.go | 51 +++++++ .../integration/apigateway/apigateway_test.go | 75 +++++++++++ adapters/integration/apigateway/create.go | 32 +++++ adapters/integration/apigateway/find.go | 21 +++ adapters/integration/apigateway/setup.go | 7 + proc/proc.go | 1 + 7 files changed, 313 insertions(+) create mode 100644 adapters/apigateway-authorizer.go create mode 100644 adapters/apigateway-authorizer_test.go diff --git a/adapters/apigateway-authorizer.go b/adapters/apigateway-authorizer.go new file mode 100644 index 00000000..a1bfc5d2 --- /dev/null +++ b/adapters/apigateway-authorizer.go @@ -0,0 +1,126 @@ +package adapters + +import ( + "context" + "fmt" + "strings" + + "github.com/aws/aws-sdk-go-v2/service/apigateway" + "github.com/aws/aws-sdk-go-v2/service/apigateway/types" + "github.com/overmindtech/aws-source/adapterhelpers" + "github.com/overmindtech/sdp-go" +) + +// convertGetAuthorizerOutputToAuthorizer converts a GetAuthorizerOutput to an Authorizer +func convertGetAuthorizerOutputToAuthorizer(output *apigateway.GetAuthorizerOutput) *types.Authorizer { + return &types.Authorizer{ + Id: output.Id, + Name: output.Name, + Type: output.Type, + ProviderARNs: output.ProviderARNs, + AuthType: output.AuthType, + AuthorizerUri: output.AuthorizerUri, + AuthorizerCredentials: output.AuthorizerCredentials, + IdentitySource: output.IdentitySource, + IdentityValidationExpression: output.IdentityValidationExpression, + AuthorizerResultTtlInSeconds: output.AuthorizerResultTtlInSeconds, + } +} + +func authorizerOutputMapper(scope string, awsItem *types.Authorizer) (*sdp.Item, error) { + attributes, err := adapterhelpers.ToAttributesWithExclude(awsItem, "tags") + if err != nil { + return nil, err + } + + item := sdp.Item{ + Type: "apigateway-authorizer", + UniqueAttribute: "Id", + Attributes: attributes, + Scope: scope, + } + + return &item, nil +} + +func NewAPIGatewayAuthorizerAdapter(client *apigateway.Client, accountID string, region string) *adapterhelpers.GetListAdapter[*types.Authorizer, *apigateway.Client, *apigateway.Options] { + return &adapterhelpers.GetListAdapter[*types.Authorizer, *apigateway.Client, *apigateway.Options]{ + ItemType: "apigateway-authorizer", + Client: client, + AccountID: accountID, + Region: region, + AdapterMetadata: authorizerAdapterMetadata, + GetFunc: func(ctx context.Context, client *apigateway.Client, scope, query string) (*types.Authorizer, error) { + f := strings.Split(query, "/") + if len(f) != 2 { + return nil, &sdp.QueryError{ + ErrorType: sdp.QueryError_NOTFOUND, + ErrorString: fmt.Sprintf("query must be in the format of: the rest-api-id/authorizer-id, but found: %s", query), + } + } + out, err := client.GetAuthorizer(ctx, &apigateway.GetAuthorizerInput{ + RestApiId: &f[0], + AuthorizerId: &f[1], + }) + if err != nil { + return nil, err + } + return convertGetAuthorizerOutputToAuthorizer(out), nil + }, + DisableList: true, + SearchFunc: func(ctx context.Context, client *apigateway.Client, scope string, query string) ([]*types.Authorizer, error) { + f := strings.Split(query, "/") + var restAPIID string + var name string + + switch len(f) { + case 1: + restAPIID = f[0] + case 2: + restAPIID = f[0] + name = f[1] + default: + return nil, &sdp.QueryError{ + ErrorType: sdp.QueryError_NOTFOUND, + ErrorString: fmt.Sprintf( + "query must be in the format of: the rest-api-id/authorizer-id or rest-api-id, but found: %s", + query, + ), + } + } + + out, err := client.GetAuthorizers(ctx, &apigateway.GetAuthorizersInput{ + RestApiId: &restAPIID, + }) + if err != nil { + return nil, err + } + + var items []*types.Authorizer + for _, authorizer := range out.Items { + if name != "" && strings.Contains(*authorizer.Name, name) { + items = append(items, &authorizer) + } else { + items = append(items, &authorizer) + } + } + + return items, nil + }, + ItemMapper: func(_, scope string, awsItem *types.Authorizer) (*sdp.Item, error) { + return authorizerOutputMapper(scope, awsItem) + }, + } +} + +var authorizerAdapterMetadata = Metadata.Register(&sdp.AdapterMetadata{ + Type: "apigateway-authorizer", + DescriptiveName: "API Gateway Authorizer", + Category: sdp.AdapterCategory_ADAPTER_CATEGORY_SECURITY, + SupportedQueryMethods: &sdp.AdapterSupportedQueryMethods{ + Get: true, + Search: true, + GetDescription: "Get an API Gateway Authorizer by its rest API ID and ID: rest-api-id/authorizer-id", + SearchDescription: "Search for API Gateway Authorizers by their rest API ID or with rest API ID and their name: rest-api-id/authorizer-name", + }, +}) diff --git a/adapters/apigateway-authorizer_test.go b/adapters/apigateway-authorizer_test.go new file mode 100644 index 00000000..aa5ffee9 --- /dev/null +++ b/adapters/apigateway-authorizer_test.go @@ -0,0 +1,51 @@ +package adapters + +import ( + "testing" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/apigateway" + "github.com/aws/aws-sdk-go-v2/service/apigateway/types" + "github.com/overmindtech/aws-source/adapterhelpers" +) + +func TestAuthorizerOutputMapper(t *testing.T) { + awsItem := &types.Authorizer{ + Id: aws.String("authorizer-id"), + Name: aws.String("authorizer-name"), + Type: types.AuthorizerTypeRequest, + ProviderARNs: []string{"arn:aws:iam::123456789012:role/service-role"}, + AuthType: aws.String("custom"), + AuthorizerUri: aws.String("arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:123456789012:function:my-function/invocations"), + AuthorizerCredentials: aws.String("arn:aws:iam::123456789012:role/service-role"), + IdentitySource: aws.String("method.request.header.Authorization"), + IdentityValidationExpression: aws.String(".*"), + AuthorizerResultTtlInSeconds: aws.Int32(300), + } + + item, err := authorizerOutputMapper("scope", awsItem) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if err := item.Validate(); err != nil { + t.Error(err) + } +} + +func TestNewAPIGatewayAuthorizerAdapter(t *testing.T) { + config, account, region := adapterhelpers.GetAutoConfig(t) + + client := apigateway.NewFromConfig(config) + + adapter := NewAPIGatewayAuthorizerAdapter(client, account, region) + + test := adapterhelpers.E2ETest{ + Adapter: adapter, + Timeout: 10 * time.Second, + SkipList: true, + } + + test.Run(t) +} diff --git a/adapters/integration/apigateway/apigateway_test.go b/adapters/integration/apigateway/apigateway_test.go index 4c724180..b174efbe 100644 --- a/adapters/integration/apigateway/apigateway_test.go +++ b/adapters/integration/apigateway/apigateway_test.go @@ -71,6 +71,13 @@ func APIGateway(t *testing.T) { t.Fatalf("failed to validate APIGateway API key adapter: %v", err) } + authorizerSource := adapters.NewAPIGatewayAuthorizerAdapter(testClient, accountID, testAWSConfig.Region) + + err = authorizerSource.Validate() + if err != nil { + t.Fatalf("failed to validate APIGateway authorizer adapter: %v", err) + } + scope := adapterhelpers.FormatScope(accountID, testAWSConfig.Region) // List restApis @@ -301,5 +308,73 @@ func APIGateway(t *testing.T) { t.Fatalf("expected API key ID %s, got %s", apiKeyID, apiKeyIDFromSearch) } + // Search authorizers by restApiID + authorizers, err := authorizerSource.Search(ctx, scope, restApiID, true) + if err != nil { + t.Fatalf("failed to search APIGateway authorizers: %v", err) + } + + authorizerUniqueAttribute := authorizers[0].GetUniqueAttribute() + + authorizerTestName := integration.ResourceName(integration.APIGateway, authorizerSrc, integration.TestID()) + authorizerID, err := integration.GetUniqueAttributeValueBySignificantAttribute( + authorizerUniqueAttribute, + "Name", + authorizerTestName, + authorizers, + true, + ) + if err != nil { + t.Fatalf("failed to get authorizer ID: %v", err) + } + + // Get authorizer + query := fmt.Sprintf("%s/%s", restApiID, authorizerID) + authorizer, err := authorizerSource.Get(ctx, scope, query, true) + if err != nil { + t.Fatalf("failed to get APIGateway authorizer: %v", err) + } + + authorizerIDFromGet, err := integration.GetUniqueAttributeValueBySignificantAttribute( + authorizerUniqueAttribute, + "Name", + authorizerTestName, + []*sdp.Item{authorizer}, + true, + ) + if err != nil { + t.Fatalf("failed to get authorizer ID from get: %v", err) + } + + if authorizerID != authorizerIDFromGet { + t.Fatalf("expected authorizer ID %s, got %s", authorizerID, authorizerIDFromGet) + } + + // Search authorizer by restApiID/name + query = fmt.Sprintf("%s/%s", restApiID, authorizerTestName) + authorizersFromSearch, err := authorizerSource.Search(ctx, scope, query, true) + if err != nil { + t.Fatalf("failed to search APIGateway authorizers: %v", err) + } + + if len(authorizersFromSearch) == 0 { + t.Fatalf("no authorizers found") + } + + authorizerIDFromSearch, err := integration.GetUniqueAttributeValueBySignificantAttribute( + authorizerUniqueAttribute, + "Name", + authorizerTestName, + authorizersFromSearch, + true, + ) + if err != nil { + t.Fatalf("failed to get authorizer ID from search: %v", err) + } + + if authorizerID != authorizerIDFromSearch { + t.Fatalf("expected authorizer ID %s, got %s", authorizerID, authorizerIDFromSearch) + } + t.Log("APIGateway integration test completed") } diff --git a/adapters/integration/apigateway/create.go b/adapters/integration/apigateway/create.go index 21ab5ddb..b890af44 100644 --- a/adapters/integration/apigateway/create.go +++ b/adapters/integration/apigateway/create.go @@ -3,6 +3,7 @@ package apigateway import ( "context" "errors" + "github.com/aws/aws-sdk-go-v2/service/apigateway/types" "log/slog" "strings" @@ -196,3 +197,34 @@ func createAPIKey(ctx context.Context, logger *slog.Logger, client *apigateway.C return nil } + +func createAuthorizer(ctx context.Context, logger *slog.Logger, client *apigateway.Client, restAPIID, testID string) error { + // check if an authorizer with the same name already exists + id, err := findAuthorizerByName(ctx, client, restAPIID, integration.ResourceName(integration.APIGateway, authorizerSrc, testID)) + if err != nil { + if errors.As(err, new(integration.NotFoundError)) { + logger.InfoContext(ctx, "Creating authorizer") + } else { + return err + } + } + + if id != nil { + logger.InfoContext(ctx, "Authorizer already exists") + return nil + } + + identitySource := "method.request.header.Authorization" + _, err = client.CreateAuthorizer(ctx, &apigateway.CreateAuthorizerInput{ + RestApiId: &restAPIID, + Name: adapterhelpers.PtrString(integration.ResourceName(integration.APIGateway, authorizerSrc, testID)), + Type: types.AuthorizerTypeToken, + IdentitySource: &identitySource, + AuthorizerUri: adapterhelpers.PtrString("arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:123456789012:function:auth-function/invocations"), + }) + if err != nil { + return err + } + + return nil +} diff --git a/adapters/integration/apigateway/find.go b/adapters/integration/apigateway/find.go index 7d8eb309..1b3671ee 100644 --- a/adapters/integration/apigateway/find.go +++ b/adapters/integration/apigateway/find.go @@ -131,3 +131,24 @@ func findAPIKeyByName(ctx context.Context, client *apigateway.Client, name strin return nil, integration.NewNotFoundError(integration.ResourceName(integration.APIGateway, apiKeySrc, name)) } + +func findAuthorizerByName(ctx context.Context, client *apigateway.Client, restAPIID, name string) (*string, error) { + result, err := client.GetAuthorizers(ctx, &apigateway.GetAuthorizersInput{ + RestApiId: &restAPIID, + }) + if err != nil { + return nil, err + } + + if len(result.Items) == 0 { + return nil, integration.NewNotFoundError(integration.ResourceName(integration.APIGateway, authorizerSrc, name)) + } + + for _, authorizer := range result.Items { + if *authorizer.Name == name { + return authorizer.Id, nil + } + } + + return nil, integration.NewNotFoundError(integration.ResourceName(integration.APIGateway, authorizerSrc, name)) +} diff --git a/adapters/integration/apigateway/setup.go b/adapters/integration/apigateway/setup.go index 021a2f65..5ec69314 100644 --- a/adapters/integration/apigateway/setup.go +++ b/adapters/integration/apigateway/setup.go @@ -15,6 +15,7 @@ const ( methodResponseSrc = "method-response" integrationSrc = "integration" apiKeySrc = "api-key" + authorizerSrc = "authorizer" ) func setup(ctx context.Context, logger *slog.Logger, client *apigateway.Client) error { @@ -62,5 +63,11 @@ func setup(ctx context.Context, logger *slog.Logger, client *apigateway.Client) return err } + // Create Authorizer + err = createAuthorizer(ctx, logger, client, *restApiID, testID) + if err != nil { + return err + } + return nil } diff --git a/proc/proc.go b/proc/proc.go index 00885f09..b4fd884a 100644 --- a/proc/proc.go +++ b/proc/proc.go @@ -484,6 +484,7 @@ func InitializeAwsSourceEngine(ctx context.Context, ec *discovery.EngineConfig, adapters.NewAPIGatewayIntegrationAdapter(apigatewayClient, *callerID.Account, cfg.Region), adapters.NewAPIGatewayVpcLinkAdapter(apigatewayClient, *callerID.Account, cfg.Region), adapters.NewAPIGatewayApiKeyAdapter(apigatewayClient, *callerID.Account, cfg.Region), + adapters.NewAPIGatewayAuthorizerAdapter(apigatewayClient, *callerID.Account, cfg.Region), // SSM adapters.NewSSMParameterAdapter(ssmClient, *callerID.Account, cfg.Region), From a102a72dc6a783344dbfe9d2fb9b92fc47e94aa8 Mon Sep 17 00:00:00 2001 From: tomas Date: Tue, 14 Jan 2025 22:16:44 +0000 Subject: [PATCH 05/12] add apigateway deployment adapter --- adapters/apigateway-deployment.go | 120 ++++++++++++++++++ adapters/apigateway-deployment_test.go | 45 +++++++ .../integration/apigateway/apigateway_test.go | 82 ++++++++++++ adapters/integration/apigateway/create.go | 27 ++++ adapters/integration/apigateway/find.go | 17 +++ adapters/integration/apigateway/setup.go | 7 + proc/proc.go | 1 + 7 files changed, 299 insertions(+) create mode 100644 adapters/apigateway-deployment.go create mode 100644 adapters/apigateway-deployment_test.go diff --git a/adapters/apigateway-deployment.go b/adapters/apigateway-deployment.go new file mode 100644 index 00000000..20bbe92d --- /dev/null +++ b/adapters/apigateway-deployment.go @@ -0,0 +1,120 @@ +package adapters + +import ( + "context" + "fmt" + "strings" + + "github.com/aws/aws-sdk-go-v2/service/apigateway" + "github.com/aws/aws-sdk-go-v2/service/apigateway/types" + "github.com/overmindtech/aws-source/adapterhelpers" + "github.com/overmindtech/sdp-go" +) + +// convertGetDeploymentOutputToDeployment converts a GetDeploymentOutput to a Deployment +func convertGetDeploymentOutputToDeployment(output *apigateway.GetDeploymentOutput) *types.Deployment { + return &types.Deployment{ + Id: output.Id, + CreatedDate: output.CreatedDate, + Description: output.Description, + ApiSummary: output.ApiSummary, + } +} + +func deploymentOutputMapper(scope string, awsItem *types.Deployment) (*sdp.Item, error) { + attributes, err := adapterhelpers.ToAttributesWithExclude(awsItem, "tags") + if err != nil { + return nil, err + } + + item := sdp.Item{ + Type: "apigateway-deployment", + UniqueAttribute: "Id", + Attributes: attributes, + Scope: scope, + } + + return &item, nil +} + +func NewAPIGatewayDeploymentAdapter(client *apigateway.Client, accountID string, region string) *adapterhelpers.GetListAdapter[*types.Deployment, *apigateway.Client, *apigateway.Options] { + return &adapterhelpers.GetListAdapter[*types.Deployment, *apigateway.Client, *apigateway.Options]{ + ItemType: "apigateway-deployment", + Client: client, + AccountID: accountID, + Region: region, + AdapterMetadata: deploymentAdapterMetadata, + GetFunc: func(ctx context.Context, client *apigateway.Client, scope, query string) (*types.Deployment, error) { + f := strings.Split(query, "/") + if len(f) != 2 { + return nil, &sdp.QueryError{ + ErrorType: sdp.QueryError_NOTFOUND, + ErrorString: fmt.Sprintf("query must be in the format of: the rest-api-id/deployment-id, but found: %s", query), + } + } + out, err := client.GetDeployment(ctx, &apigateway.GetDeploymentInput{ + RestApiId: &f[0], + DeploymentId: &f[1], + }) + if err != nil { + return nil, err + } + return convertGetDeploymentOutputToDeployment(out), nil + }, + DisableList: true, + SearchFunc: func(ctx context.Context, client *apigateway.Client, scope string, query string) ([]*types.Deployment, error) { + f := strings.Split(query, "/") + var restAPIID string + var description string + + switch len(f) { + case 1: + restAPIID = f[0] + case 2: + restAPIID = f[0] + description = f[1] + default: + return nil, &sdp.QueryError{ + ErrorType: sdp.QueryError_NOTFOUND, + ErrorString: fmt.Sprintf( + "query must be in the format of: the rest-api-id/deployment-id or rest-api-id, but found: %s", + query, + ), + } + } + + out, err := client.GetDeployments(ctx, &apigateway.GetDeploymentsInput{ + RestApiId: &restAPIID, + }) + if err != nil { + return nil, err + } + + var items []*types.Deployment + for _, deployment := range out.Items { + if description != "" && strings.Contains(*deployment.Description, description) { + items = append(items, &deployment) + } else { + items = append(items, &deployment) + } + } + + return items, nil + }, + ItemMapper: func(_, scope string, awsItem *types.Deployment) (*sdp.Item, error) { + return deploymentOutputMapper(scope, awsItem) + }, + } +} + +var deploymentAdapterMetadata = Metadata.Register(&sdp.AdapterMetadata{ + Type: "apigateway-deployment", + DescriptiveName: "API Gateway Deployment", + Category: sdp.AdapterCategory_ADAPTER_CATEGORY_CONFIGURATION, + SupportedQueryMethods: &sdp.AdapterSupportedQueryMethods{ + Get: true, + Search: true, + GetDescription: "Get an API Gateway Deployment by its rest API ID and ID: rest-api-id/deployment-id", + SearchDescription: "Search for API Gateway Deployments by their rest API ID or with rest API ID and their description: rest-api-id/deployment-description", + }, +}) diff --git a/adapters/apigateway-deployment_test.go b/adapters/apigateway-deployment_test.go new file mode 100644 index 00000000..fb6da910 --- /dev/null +++ b/adapters/apigateway-deployment_test.go @@ -0,0 +1,45 @@ +package adapters + +import ( + "testing" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/apigateway" + "github.com/aws/aws-sdk-go-v2/service/apigateway/types" + "github.com/overmindtech/aws-source/adapterhelpers" +) + +func TestDeploymentOutputMapper(t *testing.T) { + awsItem := &types.Deployment{ + Id: aws.String("deployment-id"), + CreatedDate: aws.Time(time.Now()), + Description: aws.String("deployment-description"), + ApiSummary: map[string]map[string]types.MethodSnapshot{}, + } + + item, err := deploymentOutputMapper("scope", awsItem) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if err := item.Validate(); err != nil { + t.Error(err) + } +} + +func TestNewAPIGatewayDeploymentAdapter(t *testing.T) { + config, account, region := adapterhelpers.GetAutoConfig(t) + + client := apigateway.NewFromConfig(config) + + adapter := NewAPIGatewayDeploymentAdapter(client, account, region) + + test := adapterhelpers.E2ETest{ + Adapter: adapter, + Timeout: 10 * time.Second, + SkipList: true, + } + + test.Run(t) +} diff --git a/adapters/integration/apigateway/apigateway_test.go b/adapters/integration/apigateway/apigateway_test.go index b174efbe..8cc33b66 100644 --- a/adapters/integration/apigateway/apigateway_test.go +++ b/adapters/integration/apigateway/apigateway_test.go @@ -29,6 +29,8 @@ func APIGateway(t *testing.T) { t.Log("Running APIGateway integration test") + // Resources ------------------------------------------------------------------------------------------------------ + restApiSource := adapters.NewAPIGatewayRestApiAdapter(testClient, accountID, testAWSConfig.Region) err = restApiSource.Validate() @@ -78,6 +80,15 @@ func APIGateway(t *testing.T) { t.Fatalf("failed to validate APIGateway authorizer adapter: %v", err) } + deploymentSource := adapters.NewAPIGatewayDeploymentAdapter(testClient, accountID, testAWSConfig.Region) + + err = deploymentSource.Validate() + if err != nil { + t.Fatalf("failed to validate APIGateway deployment adapter: %v", err) + } + + // Tests ---------------------------------------------------------------------------------------------------------- + scope := adapterhelpers.FormatScope(accountID, testAWSConfig.Region) // List restApis @@ -376,5 +387,76 @@ func APIGateway(t *testing.T) { t.Fatalf("expected authorizer ID %s, got %s", authorizerID, authorizerIDFromSearch) } + // Search deployments by restApiID + deployments, err := deploymentSource.Search(ctx, scope, restApiID, true) + if err != nil { + t.Fatalf("failed to search APIGateway deployments: %v", err) + } + + if len(deployments) == 0 { + t.Fatalf("no deployments found") + } + + deploymentUniqueAttribute := deployments[0].GetUniqueAttribute() + + deploymentID, err := integration.GetUniqueAttributeValueBySignificantAttribute( + deploymentUniqueAttribute, + "Description", + "test-deployment", + deployments, + true, + ) + if err != nil { + t.Fatalf("failed to get deployment ID: %v", err) + } + + // Get deployment + query = fmt.Sprintf("%s/%s", restApiID, deploymentID) + deployment, err := deploymentSource.Get(ctx, scope, query, true) + if err != nil { + t.Fatalf("failed to get APIGateway deployment: %v", err) + } + + deploymentIDFromGet, err := integration.GetUniqueAttributeValueBySignificantAttribute( + deploymentUniqueAttribute, + "Description", + "test-deployment", + []*sdp.Item{deployment}, + true, + ) + if err != nil { + t.Fatalf("failed to get deployment ID from get: %v", err) + } + + if deploymentID != deploymentIDFromGet { + t.Fatalf("expected deployment ID %s, got %s", deploymentID, deploymentIDFromGet) + } + + // Search deployment by restApiID/description + query = fmt.Sprintf("%s/test-deployment", restApiID) + deploymentsFromSearch, err := deploymentSource.Search(ctx, scope, query, true) + if err != nil { + t.Fatalf("failed to search APIGateway deployments: %v", err) + } + + if len(deploymentsFromSearch) == 0 { + t.Fatalf("no deployments found") + } + + deploymentIDFromSearch, err := integration.GetUniqueAttributeValueBySignificantAttribute( + deploymentUniqueAttribute, + "Description", + "test-deployment", + deploymentsFromSearch, + true, + ) + if err != nil { + t.Fatalf("failed to get deployment ID from search: %v", err) + } + + if deploymentID != deploymentIDFromSearch { + t.Fatalf("expected deployment ID %s, got %s", deploymentID, deploymentIDFromSearch) + } + t.Log("APIGateway integration test completed") } diff --git a/adapters/integration/apigateway/create.go b/adapters/integration/apigateway/create.go index b890af44..0ae62e0a 100644 --- a/adapters/integration/apigateway/create.go +++ b/adapters/integration/apigateway/create.go @@ -228,3 +228,30 @@ func createAuthorizer(ctx context.Context, logger *slog.Logger, client *apigatew return nil } + +func createDeployment(ctx context.Context, logger *slog.Logger, client *apigateway.Client, restAPIID string) error { + // check if a deployment with the same name already exists + id, err := findDeploymentByDescription(ctx, client, restAPIID, "test-deployment") + if err != nil { + if errors.As(err, new(integration.NotFoundError)) { + logger.InfoContext(ctx, "Creating deployment") + } else { + return err + } + } + + if id != nil { + logger.InfoContext(ctx, "Deployment already exists") + return nil + } + + _, err = client.CreateDeployment(ctx, &apigateway.CreateDeploymentInput{ + RestApiId: &restAPIID, + Description: adapterhelpers.PtrString("test-deployment"), + }) + if err != nil { + return err + } + + return nil +} diff --git a/adapters/integration/apigateway/find.go b/adapters/integration/apigateway/find.go index 1b3671ee..2d64270e 100644 --- a/adapters/integration/apigateway/find.go +++ b/adapters/integration/apigateway/find.go @@ -152,3 +152,20 @@ func findAuthorizerByName(ctx context.Context, client *apigateway.Client, restAP return nil, integration.NewNotFoundError(integration.ResourceName(integration.APIGateway, authorizerSrc, name)) } + +func findDeploymentByDescription(ctx context.Context, client *apigateway.Client, restAPIID, description string) (*string, error) { + result, err := client.GetDeployments(ctx, &apigateway.GetDeploymentsInput{ + RestApiId: &restAPIID, + }) + if err != nil { + return nil, err + } + + for _, deployment := range result.Items { + if *deployment.Description == description { + return deployment.Id, nil + } + } + + return nil, integration.NewNotFoundError(integration.ResourceName(integration.APIGateway, deploymentSrc, description)) +} diff --git a/adapters/integration/apigateway/setup.go b/adapters/integration/apigateway/setup.go index 5ec69314..5ad33757 100644 --- a/adapters/integration/apigateway/setup.go +++ b/adapters/integration/apigateway/setup.go @@ -16,6 +16,7 @@ const ( integrationSrc = "integration" apiKeySrc = "api-key" authorizerSrc = "authorizer" + deploymentSrc = "deployment" ) 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) return err } + // Create Deployment + err = createDeployment(ctx, logger, client, *restApiID) + if err != nil { + return err + } + return nil } diff --git a/proc/proc.go b/proc/proc.go index b4fd884a..d5979c3f 100644 --- a/proc/proc.go +++ b/proc/proc.go @@ -485,6 +485,7 @@ func InitializeAwsSourceEngine(ctx context.Context, ec *discovery.EngineConfig, adapters.NewAPIGatewayVpcLinkAdapter(apigatewayClient, *callerID.Account, cfg.Region), adapters.NewAPIGatewayApiKeyAdapter(apigatewayClient, *callerID.Account, cfg.Region), adapters.NewAPIGatewayAuthorizerAdapter(apigatewayClient, *callerID.Account, cfg.Region), + adapters.NewAPIGatewayDeploymentAdapter(apigatewayClient, *callerID.Account, cfg.Region), // SSM adapters.NewSSMParameterAdapter(ssmClient, *callerID.Account, cfg.Region), From 9754ffa6da15279b0cc3d5f6d85fcf349c743838 Mon Sep 17 00:00:00 2001 From: tomas Date: Wed, 15 Jan 2025 22:20:07 +0000 Subject: [PATCH 06/12] add apigateway stage adapter --- adapters/apigateway-stage.go | 154 ++++++++++++++++++++++++++++++ adapters/apigateway-stage_test.go | 73 ++++++++++++++ proc/proc.go | 1 + 3 files changed, 228 insertions(+) create mode 100644 adapters/apigateway-stage.go create mode 100644 adapters/apigateway-stage_test.go diff --git a/adapters/apigateway-stage.go b/adapters/apigateway-stage.go new file mode 100644 index 00000000..97fe4732 --- /dev/null +++ b/adapters/apigateway-stage.go @@ -0,0 +1,154 @@ +package adapters + +import ( + "context" + "fmt" + "strings" + + "github.com/aws/aws-sdk-go-v2/service/apigateway" + "github.com/aws/aws-sdk-go-v2/service/apigateway/types" + "github.com/overmindtech/aws-source/adapterhelpers" + "github.com/overmindtech/sdp-go" +) + +func convertGetStageOutputToStage(output *apigateway.GetStageOutput) *types.Stage { + return &types.Stage{ + DeploymentId: output.DeploymentId, + StageName: output.StageName, + Description: output.Description, + CreatedDate: output.CreatedDate, + LastUpdatedDate: output.LastUpdatedDate, + Variables: output.Variables, + AccessLogSettings: output.AccessLogSettings, + CacheClusterEnabled: output.CacheClusterEnabled, + CacheClusterSize: output.CacheClusterSize, + CacheClusterStatus: output.CacheClusterStatus, + CanarySettings: output.CanarySettings, + ClientCertificateId: output.ClientCertificateId, + DocumentationVersion: output.DocumentationVersion, + MethodSettings: output.MethodSettings, + TracingEnabled: output.TracingEnabled, + WebAclArn: output.WebAclArn, + Tags: output.Tags, + } +} + +func stageOutputMapper(query, scope string, awsItem *types.Stage) (*sdp.Item, error) { + attributes, err := adapterhelpers.ToAttributesWithExclude(awsItem, "tags") + if err != nil { + return nil, err + } + + // if it is `GET`, the query will be: rest-api-id/stage-name + // if it is `SEARCH`, the query will be: rest-api-id/deployment-id or rest-api-id + restAPIID := strings.Split(query, "/")[0] + + err = attributes.Set("UniqueAttribute", fmt.Sprintf("%s/%s", restAPIID, *awsItem.StageName)) + + item := sdp.Item{ + Type: "apigateway-stage", + UniqueAttribute: "StageName", + Attributes: attributes, + Scope: scope, + Tags: awsItem.Tags, + } + + if awsItem.DeploymentId != nil { + item.LinkedItemQueries = append(item.LinkedItemQueries, &sdp.LinkedItemQuery{ + Query: &sdp.Query{ + Type: "apigateway-deployment", + Method: sdp.QueryMethod_GET, + Query: fmt.Sprintf("%s/%s", restAPIID, *awsItem.DeploymentId), + Scope: scope, + }, + BlastPropagation: &sdp.BlastPropagation{ + // Deleting a deployment will impact the stage + In: true, + // Deleting a stage won't impact the deployment + Out: false, + }, + }) + } + + return &item, nil +} + +func NewAPIGatewayStageAdapter(client *apigateway.Client, accountID string, region string) *adapterhelpers.GetListAdapter[*types.Stage, *apigateway.Client, *apigateway.Options] { + return &adapterhelpers.GetListAdapter[*types.Stage, *apigateway.Client, *apigateway.Options]{ + ItemType: "apigateway-stage", + Client: client, + AccountID: accountID, + Region: region, + AdapterMetadata: stageAdapterMetadata, + GetFunc: func(ctx context.Context, client *apigateway.Client, scope, query string) (*types.Stage, error) { + f := strings.Split(query, "/") + if len(f) != 2 { + return nil, &sdp.QueryError{ + ErrorType: sdp.QueryError_NOTFOUND, + ErrorString: fmt.Sprintf("query must be in the format of: the rest-api-id/stage-name, but found: %s", query), + } + } + out, err := client.GetStage(ctx, &apigateway.GetStageInput{ + RestApiId: &f[0], + StageName: &f[1], + }) + if err != nil { + return nil, err + } + return convertGetStageOutputToStage(out), nil + }, + DisableList: true, + SearchFunc: func(ctx context.Context, client *apigateway.Client, scope string, query string) ([]*types.Stage, error) { + f := strings.Split(query, "/") + var restAPIID string + var deploymentID string + + switch len(f) { + case 1: + restAPIID = f[0] + case 2: + restAPIID = f[0] + deploymentID = f[1] + default: + return nil, &sdp.QueryError{ + ErrorType: sdp.QueryError_NOTFOUND, + ErrorString: fmt.Sprintf( + "query must be in the format of: the rest-api-id/deployment-id or rest-api-id, but found: %s", + query, + ), + } + } + + out, err := client.GetStages(ctx, &apigateway.GetStagesInput{ + RestApiId: &restAPIID, + DeploymentId: &deploymentID, + }) + if err != nil { + return nil, err + } + + var items []*types.Stage + for _, stage := range out.Item { + items = append(items, &stage) + } + + return items, nil + }, + ItemMapper: func(query, scope string, awsItem *types.Stage) (*sdp.Item, error) { + return stageOutputMapper(query, scope, awsItem) + }, + } +} + +var stageAdapterMetadata = Metadata.Register(&sdp.AdapterMetadata{ + Type: "apigateway-stage", + DescriptiveName: "API Gateway Stage", + Category: sdp.AdapterCategory_ADAPTER_CATEGORY_CONFIGURATION, + SupportedQueryMethods: &sdp.AdapterSupportedQueryMethods{ + Get: true, + Search: true, + GetDescription: "Get an API Gateway Stage by its rest API ID and stage name: rest-api-id/stage-name", + SearchDescription: "Search for API Gateway Stages by their rest API ID or with rest API ID and their stage name: rest-api-id/stage-name", + }, + PotentialLinks: []string{"wafv2-web-acl"}, +}) diff --git a/adapters/apigateway-stage_test.go b/adapters/apigateway-stage_test.go new file mode 100644 index 00000000..bae7bbce --- /dev/null +++ b/adapters/apigateway-stage_test.go @@ -0,0 +1,73 @@ +package adapters + +import ( + "github.com/overmindtech/sdp-go" + "testing" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/apigateway" + "github.com/aws/aws-sdk-go-v2/service/apigateway/types" + "github.com/overmindtech/aws-source/adapterhelpers" +) + +func TestStageOutputMapper(t *testing.T) { + awsItem := &types.Stage{ + DeploymentId: aws.String("deployment-id"), + StageName: aws.String("stage-name"), + Description: aws.String("description"), + CreatedDate: aws.Time(time.Now()), + LastUpdatedDate: aws.Time(time.Now()), + Variables: map[string]string{"key": "value"}, + AccessLogSettings: &types.AccessLogSettings{}, + CacheClusterEnabled: true, + CacheClusterSize: "0.5", + CacheClusterStatus: types.CacheClusterStatusAvailable, + CanarySettings: &types.CanarySettings{}, + ClientCertificateId: aws.String("client-cert-id"), + DocumentationVersion: aws.String("1.0"), + MethodSettings: map[string]types.MethodSetting{}, + TracingEnabled: true, + WebAclArn: aws.String("web-acl-arn"), + Tags: map[string]string{"tag-key": "tag-value"}, + } + + queries := []string{"rest-api-id/stage-name", "rest-api-id/deployment-id", "rest-api-id"} + for _, query := range queries { + item, err := stageOutputMapper(query, "scope", awsItem) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if err := item.Validate(); err != nil { + t.Error(err) + } + + tests := adapterhelpers.QueryTests{ + { + ExpectedType: "apigateway-deployment", + ExpectedMethod: sdp.QueryMethod_GET, + ExpectedQuery: "rest-api-id/deployment-id", + ExpectedScope: "scope", + }, + } + + tests.Execute(t, item) + } +} + +func TestNewAPIGatewayStageAdapter(t *testing.T) { + config, account, region := adapterhelpers.GetAutoConfig(t) + + client := apigateway.NewFromConfig(config) + + adapter := NewAPIGatewayStageAdapter(client, account, region) + + test := adapterhelpers.E2ETest{ + Adapter: adapter, + Timeout: 10 * time.Second, + SkipList: true, + } + + test.Run(t) +} diff --git a/proc/proc.go b/proc/proc.go index d5979c3f..7c491262 100644 --- a/proc/proc.go +++ b/proc/proc.go @@ -486,6 +486,7 @@ func InitializeAwsSourceEngine(ctx context.Context, ec *discovery.EngineConfig, adapters.NewAPIGatewayApiKeyAdapter(apigatewayClient, *callerID.Account, cfg.Region), adapters.NewAPIGatewayAuthorizerAdapter(apigatewayClient, *callerID.Account, cfg.Region), adapters.NewAPIGatewayDeploymentAdapter(apigatewayClient, *callerID.Account, cfg.Region), + adapters.NewAPIGatewayStageAdapter(apigatewayClient, *callerID.Account, cfg.Region), // SSM adapters.NewSSMParameterAdapter(ssmClient, *callerID.Account, cfg.Region), From 2bdaf6be1813d609ebbe5a2d54799e3c37460109 Mon Sep 17 00:00:00 2001 From: tomas Date: Thu, 16 Jan 2025 23:19:41 +0000 Subject: [PATCH 07/12] add apigateway stage integration tests --- adapters/apigateway-stage.go | 20 ++--- .../integration/apigateway/apigateway_test.go | 78 +++++++++++++++++++ adapters/integration/apigateway/create.go | 37 ++++++++- adapters/integration/apigateway/find.go | 23 ++++++ adapters/integration/apigateway/setup.go | 9 ++- 5 files changed, 152 insertions(+), 15 deletions(-) diff --git a/adapters/apigateway-stage.go b/adapters/apigateway-stage.go index 97fe4732..a168242c 100644 --- a/adapters/apigateway-stage.go +++ b/adapters/apigateway-stage.go @@ -100,15 +100,18 @@ func NewAPIGatewayStageAdapter(client *apigateway.Client, accountID string, regi DisableList: true, SearchFunc: func(ctx context.Context, client *apigateway.Client, scope string, query string) ([]*types.Stage, error) { f := strings.Split(query, "/") - var restAPIID string - var deploymentID string + var input *apigateway.GetStagesInput switch len(f) { case 1: - restAPIID = f[0] + input = &apigateway.GetStagesInput{ + RestApiId: &f[0], + } case 2: - restAPIID = f[0] - deploymentID = f[1] + input = &apigateway.GetStagesInput{ + RestApiId: &f[0], + DeploymentId: &f[1], + } default: return nil, &sdp.QueryError{ ErrorType: sdp.QueryError_NOTFOUND, @@ -119,10 +122,7 @@ func NewAPIGatewayStageAdapter(client *apigateway.Client, accountID string, regi } } - out, err := client.GetStages(ctx, &apigateway.GetStagesInput{ - RestApiId: &restAPIID, - DeploymentId: &deploymentID, - }) + out, err := client.GetStages(ctx, input) if err != nil { return nil, err } @@ -148,7 +148,7 @@ var stageAdapterMetadata = Metadata.Register(&sdp.AdapterMetadata{ Get: true, Search: true, GetDescription: "Get an API Gateway Stage by its rest API ID and stage name: rest-api-id/stage-name", - SearchDescription: "Search for API Gateway Stages by their rest API ID or with rest API ID and their stage name: rest-api-id/stage-name", + SearchDescription: "Search for API Gateway Stages by their rest API ID or with rest API ID and deployment-id: rest-api-id/deployment-id", }, PotentialLinks: []string{"wafv2-web-acl"}, }) diff --git a/adapters/integration/apigateway/apigateway_test.go b/adapters/integration/apigateway/apigateway_test.go index 8cc33b66..8196b70b 100644 --- a/adapters/integration/apigateway/apigateway_test.go +++ b/adapters/integration/apigateway/apigateway_test.go @@ -87,6 +87,13 @@ func APIGateway(t *testing.T) { t.Fatalf("failed to validate APIGateway deployment adapter: %v", err) } + stageSource := adapters.NewAPIGatewayStageAdapter(testClient, accountID, testAWSConfig.Region) + + err = stageSource.Validate() + if err != nil { + t.Fatalf("failed to validate APIGateway stage adapter: %v", err) + } + // Tests ---------------------------------------------------------------------------------------------------------- scope := adapterhelpers.FormatScope(accountID, testAWSConfig.Region) @@ -458,5 +465,76 @@ func APIGateway(t *testing.T) { t.Fatalf("expected deployment ID %s, got %s", deploymentID, deploymentIDFromSearch) } + // Search stages by restApiID + stages, err := stageSource.Search(ctx, scope, restApiID, true) + if err != nil { + t.Fatalf("failed to search APIGateway stages: %v", err) + } + + if len(stages) == 0 { + t.Fatalf("no stages found") + } + + stageUniqueAttribute := stages[0].GetUniqueAttribute() + + stageID, err := integration.GetUniqueAttributeValueBySignificantAttribute( + stageUniqueAttribute, + "StageName", + "dev", + stages, + true, + ) + if err != nil { + t.Fatalf("failed to get stage ID: %v", err) + } + + // Get stage + query = fmt.Sprintf("%s/dev", restApiID) + stage, err := stageSource.Get(ctx, scope, query, true) + if err != nil { + t.Fatalf("failed to get APIGateway stage: %v", err) + } + + stageIDFromGet, err := integration.GetUniqueAttributeValueBySignificantAttribute( + stageUniqueAttribute, + "StageName", + "dev", + []*sdp.Item{stage}, + true, + ) + if err != nil { + t.Fatalf("failed to get stage ID from get: %v", err) + } + + if stageID != stageIDFromGet { + t.Fatalf("expected stage ID %s, got %s", stageID, stageIDFromGet) + } + + // Search stage by restApiID/deploymentID + query = fmt.Sprintf("%s/%s", restApiID, deploymentID) + stagesFromSearch, err := stageSource.Search(ctx, scope, query, true) + if err != nil { + t.Fatalf("failed to search APIGateway stages: %v", err) + } + + if len(stagesFromSearch) == 0 { + t.Fatalf("no stages found") + } + + stageIDFromSearch, err := integration.GetUniqueAttributeValueBySignificantAttribute( + stageUniqueAttribute, + "StageName", + "dev", + stagesFromSearch, + true, + ) + if err != nil { + t.Fatalf("failed to get stage ID from search: %v", err) + } + + if stageID != stageIDFromSearch { + t.Fatalf("expected stage ID %s, got %s", stageID, stageIDFromSearch) + } + t.Log("APIGateway integration test completed") } diff --git a/adapters/integration/apigateway/create.go b/adapters/integration/apigateway/create.go index 0ae62e0a..fb952843 100644 --- a/adapters/integration/apigateway/create.go +++ b/adapters/integration/apigateway/create.go @@ -229,26 +229,55 @@ func createAuthorizer(ctx context.Context, logger *slog.Logger, client *apigatew return nil } -func createDeployment(ctx context.Context, logger *slog.Logger, client *apigateway.Client, restAPIID string) error { +func createDeployment(ctx context.Context, logger *slog.Logger, client *apigateway.Client, restAPIID string) (*string, error) { // check if a deployment with the same name already exists id, err := findDeploymentByDescription(ctx, client, restAPIID, "test-deployment") if err != nil { if errors.As(err, new(integration.NotFoundError)) { logger.InfoContext(ctx, "Creating deployment") } else { - return err + return nil, err } } if id != nil { logger.InfoContext(ctx, "Deployment already exists") - return nil + return id, nil } - _, err = client.CreateDeployment(ctx, &apigateway.CreateDeploymentInput{ + resp, err := client.CreateDeployment(ctx, &apigateway.CreateDeploymentInput{ RestApiId: &restAPIID, Description: adapterhelpers.PtrString("test-deployment"), }) + if err != nil { + return nil, err + } + + return resp.Id, nil +} + +func createStage(ctx context.Context, logger *slog.Logger, client *apigateway.Client, restAPIID, deploymentID string) error { + // check if a stage with the same name already exists + stgName := "dev" + err := findStageByName(ctx, client, restAPIID, stgName) + if err != nil { + if errors.As(err, new(integration.NotFoundError)) { + logger.InfoContext(ctx, "Creating stage") + } else { + return err + } + } + + if err == nil { + logger.InfoContext(ctx, "Stage already exists") + return nil + } + + _, err = client.CreateStage(ctx, &apigateway.CreateStageInput{ + RestApiId: &restAPIID, + DeploymentId: &deploymentID, + StageName: &stgName, + }) if err != nil { return err } diff --git a/adapters/integration/apigateway/find.go b/adapters/integration/apigateway/find.go index 2d64270e..cde5bf39 100644 --- a/adapters/integration/apigateway/find.go +++ b/adapters/integration/apigateway/find.go @@ -169,3 +169,26 @@ func findDeploymentByDescription(ctx context.Context, client *apigateway.Client, return nil, integration.NewNotFoundError(integration.ResourceName(integration.APIGateway, deploymentSrc, description)) } + +func findStageByName(ctx context.Context, client *apigateway.Client, restAPIID, name string) error { + result, err := client.GetStage(ctx, &apigateway.GetStageInput{ + RestApiId: &restAPIID, + StageName: &name, + }) + if err != nil { + var notFoundErr *types.NotFoundException + if errors.As(err, ¬FoundErr) { + return integration.NewNotFoundError(integration.ResourceName( + integration.APIGateway, + stageSrc, + name, + )) + } + } + + if result == nil { + return integration.NewNotFoundError(name) + } + + return nil +} diff --git a/adapters/integration/apigateway/setup.go b/adapters/integration/apigateway/setup.go index 5ad33757..972ce0df 100644 --- a/adapters/integration/apigateway/setup.go +++ b/adapters/integration/apigateway/setup.go @@ -17,6 +17,7 @@ const ( apiKeySrc = "api-key" authorizerSrc = "authorizer" deploymentSrc = "deployment" + stageSrc = "stage" ) func setup(ctx context.Context, logger *slog.Logger, client *apigateway.Client) error { @@ -71,7 +72,13 @@ func setup(ctx context.Context, logger *slog.Logger, client *apigateway.Client) } // Create Deployment - err = createDeployment(ctx, logger, client, *restApiID) + deploymentID, err := createDeployment(ctx, logger, client, *restApiID) + if err != nil { + return err + } + + // Create Stage + err = createStage(ctx, logger, client, *restApiID, *deploymentID) if err != nil { return err } From d0b9d23e92cb64542911a2ab472820bf3e85483f Mon Sep 17 00:00:00 2001 From: tomas Date: Mon, 20 Jan 2025 23:33:06 +0000 Subject: [PATCH 08/12] add apigateway model adapter --- adapters/apigateway-model.go | 114 ++++++++++++++++++ adapters/apigateway-model_test.go | 58 +++++++++ adapters/apigateway-stage.go | 14 +++ adapters/apigateway-stage_test.go | 6 + .../integration/apigateway/apigateway_test.go | 52 ++++++++ adapters/integration/apigateway/create.go | 31 +++++ adapters/integration/apigateway/find.go | 37 +++++- adapters/integration/apigateway/setup.go | 7 ++ proc/proc.go | 1 + 9 files changed, 319 insertions(+), 1 deletion(-) create mode 100644 adapters/apigateway-model.go create mode 100644 adapters/apigateway-model_test.go diff --git a/adapters/apigateway-model.go b/adapters/apigateway-model.go new file mode 100644 index 00000000..163be8b7 --- /dev/null +++ b/adapters/apigateway-model.go @@ -0,0 +1,114 @@ +package adapters + +import ( + "context" + "fmt" + "strings" + + "github.com/aws/aws-sdk-go-v2/service/apigateway" + "github.com/aws/aws-sdk-go-v2/service/apigateway/types" + "github.com/overmindtech/aws-source/adapterhelpers" + "github.com/overmindtech/sdp-go" +) + +func convertGetModelOutputToModel(output *apigateway.GetModelOutput) *types.Model { + return &types.Model{ + Id: output.Id, + Name: output.Name, + Description: output.Description, + Schema: output.Schema, + ContentType: output.ContentType, + } +} + +func modelOutputMapper(query, scope string, awsItem *types.Model) (*sdp.Item, error) { + attributes, err := adapterhelpers.ToAttributesWithExclude(awsItem, "tags") + if err != nil { + return nil, err + } + + restAPIID := strings.Split(query, "/")[0] + + err = attributes.Set("UniqueAttribute", fmt.Sprintf("%s/%s", restAPIID, *awsItem.Name)) + + item := sdp.Item{ + Type: "apigateway-model", + UniqueAttribute: "Name", + Attributes: attributes, + Scope: scope, + } + + item.LinkedItemQueries = append(item.LinkedItemQueries, &sdp.LinkedItemQuery{ + Query: &sdp.Query{ + Type: "apigateway-rest-api", + Method: sdp.QueryMethod_GET, + Query: restAPIID, + Scope: scope, + }, + BlastPropagation: &sdp.BlastPropagation{ + // They are tightly coupled, so we need to propagate the blast to the linked item + In: true, + Out: true, + }, + }) + + return &item, nil +} + +func NewAPIGatewayModelAdapter(client *apigateway.Client, accountID string, region string) *adapterhelpers.GetListAdapter[*types.Model, *apigateway.Client, *apigateway.Options] { + return &adapterhelpers.GetListAdapter[*types.Model, *apigateway.Client, *apigateway.Options]{ + ItemType: "apigateway-model", + Client: client, + AccountID: accountID, + Region: region, + AdapterMetadata: modelAdapterMetadata, + GetFunc: func(ctx context.Context, client *apigateway.Client, scope, query string) (*types.Model, error) { + f := strings.Split(query, "/") + if len(f) != 2 { + return nil, &sdp.QueryError{ + ErrorType: sdp.QueryError_NOTFOUND, + ErrorString: fmt.Sprintf("query must be in the format of: the rest-api-id/model-name, but found: %s", query), + } + } + out, err := client.GetModel(ctx, &apigateway.GetModelInput{ + RestApiId: &f[0], + ModelName: &f[1], + }) + if err != nil { + return nil, err + } + return convertGetModelOutputToModel(out), nil + }, + DisableList: true, + SearchFunc: func(ctx context.Context, client *apigateway.Client, scope string, query string) ([]*types.Model, error) { + out, err := client.GetModels(ctx, &apigateway.GetModelsInput{ + RestApiId: &query, + }) + if err != nil { + return nil, err + } + + var items []*types.Model + for _, model := range out.Items { + items = append(items, &model) + } + + return items, nil + }, + ItemMapper: func(query, scope string, awsItem *types.Model) (*sdp.Item, error) { + return modelOutputMapper(query, scope, awsItem) + }, + } +} + +var modelAdapterMetadata = Metadata.Register(&sdp.AdapterMetadata{ + Type: "apigateway-model", + DescriptiveName: "API Gateway Model", + Category: sdp.AdapterCategory_ADAPTER_CATEGORY_CONFIGURATION, + SupportedQueryMethods: &sdp.AdapterSupportedQueryMethods{ + Get: true, + Search: true, + GetDescription: "Get an API Gateway Model by its rest API ID and model name: rest-api-id/model-name", + SearchDescription: "Search for API Gateway Models by their rest API ID: rest-api-id", + }, +}) diff --git a/adapters/apigateway-model_test.go b/adapters/apigateway-model_test.go new file mode 100644 index 00000000..119e5312 --- /dev/null +++ b/adapters/apigateway-model_test.go @@ -0,0 +1,58 @@ +package adapters + +import ( + "github.com/overmindtech/sdp-go" + "testing" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/apigateway" + "github.com/aws/aws-sdk-go-v2/service/apigateway/types" + "github.com/overmindtech/aws-source/adapterhelpers" +) + +func TestModelOutputMapper(t *testing.T) { + awsItem := &types.Model{ + Id: aws.String("model-id"), + Name: aws.String("model-name"), + Description: aws.String("description"), + Schema: aws.String("{\"type\": \"object\"}"), + ContentType: aws.String("application/json"), + } + + item, err := modelOutputMapper("rest-api-id/model-name", "scope", awsItem) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if err := item.Validate(); err != nil { + t.Error(err) + } + + tests := adapterhelpers.QueryTests{ + { + ExpectedType: "apigateway-rest-api", + ExpectedMethod: sdp.QueryMethod_GET, + ExpectedQuery: "rest-api-id", + ExpectedScope: "scope", + }, + } + + tests.Execute(t, item) +} + +func TestNewAPIGatewayModelAdapter(t *testing.T) { + config, account, region := adapterhelpers.GetAutoConfig(t) + + client := apigateway.NewFromConfig(config) + + adapter := NewAPIGatewayModelAdapter(client, account, region) + + test := adapterhelpers.E2ETest{ + Adapter: adapter, + Timeout: 10 * time.Second, + SkipList: true, + } + + test.Run(t) +} diff --git a/adapters/apigateway-stage.go b/adapters/apigateway-stage.go index a168242c..a2e420bc 100644 --- a/adapters/apigateway-stage.go +++ b/adapters/apigateway-stage.go @@ -70,6 +70,20 @@ func stageOutputMapper(query, scope string, awsItem *types.Stage) (*sdp.Item, er }) } + item.LinkedItemQueries = append(item.LinkedItemQueries, &sdp.LinkedItemQuery{ + Query: &sdp.Query{ + Type: "apigateway-rest-api", + Method: sdp.QueryMethod_GET, + Query: restAPIID, + Scope: scope, + }, + BlastPropagation: &sdp.BlastPropagation{ + // They are tightly coupled, so we need to propagate the blast to the linked item + In: true, + Out: true, + }, + }) + return &item, nil } diff --git a/adapters/apigateway-stage_test.go b/adapters/apigateway-stage_test.go index bae7bbce..c856d2aa 100644 --- a/adapters/apigateway-stage_test.go +++ b/adapters/apigateway-stage_test.go @@ -50,6 +50,12 @@ func TestStageOutputMapper(t *testing.T) { ExpectedQuery: "rest-api-id/deployment-id", ExpectedScope: "scope", }, + { + ExpectedType: "apigateway-rest-api", + ExpectedMethod: sdp.QueryMethod_GET, + ExpectedQuery: "rest-api-id", + ExpectedScope: "scope", + }, } tests.Execute(t, item) diff --git a/adapters/integration/apigateway/apigateway_test.go b/adapters/integration/apigateway/apigateway_test.go index 8196b70b..eb9d6f67 100644 --- a/adapters/integration/apigateway/apigateway_test.go +++ b/adapters/integration/apigateway/apigateway_test.go @@ -94,6 +94,13 @@ func APIGateway(t *testing.T) { t.Fatalf("failed to validate APIGateway stage adapter: %v", err) } + modelSource := adapters.NewAPIGatewayModelAdapter(testClient, accountID, testAWSConfig.Region) + + err = modelSource.Validate() + if err != nil { + t.Fatalf("failed to validate APIGateway model adapter: %v", err) + } + // Tests ---------------------------------------------------------------------------------------------------------- scope := adapterhelpers.FormatScope(accountID, testAWSConfig.Region) @@ -536,5 +543,50 @@ func APIGateway(t *testing.T) { t.Fatalf("expected stage ID %s, got %s", stageID, stageIDFromSearch) } + // Search models by restApiID + models, err := modelSource.Search(ctx, scope, restApiID, true) + if err != nil { + t.Fatalf("failed to search APIGateway models: %v", err) + } + + if len(models) == 0 { + t.Fatalf("no models found") + } + + modelUniqueAttribute := models[0].GetUniqueAttribute() + + modelID, err := integration.GetUniqueAttributeValueBySignificantAttribute( + modelUniqueAttribute, + "Name", + "testModel", + models, + true, + ) + if err != nil { + t.Fatalf("failed to get model ID: %v", err) + } + + // Get model + query = fmt.Sprintf("%s/testModel", restApiID) + model, err := modelSource.Get(ctx, scope, query, true) + if err != nil { + t.Fatalf("failed to get APIGateway model: %v", err) + } + + modelIDFromGet, err := integration.GetUniqueAttributeValueBySignificantAttribute( + modelUniqueAttribute, + "Name", + "testModel", + []*sdp.Item{model}, + true, + ) + if err != nil { + t.Fatalf("failed to get model ID from get: %v", err) + } + + if modelID != modelIDFromGet { + t.Fatalf("expected model ID %s, got %s", modelID, modelIDFromGet) + } + t.Log("APIGateway integration test completed") } diff --git a/adapters/integration/apigateway/create.go b/adapters/integration/apigateway/create.go index fb952843..2851e0d1 100644 --- a/adapters/integration/apigateway/create.go +++ b/adapters/integration/apigateway/create.go @@ -284,3 +284,34 @@ func createStage(ctx context.Context, logger *slog.Logger, client *apigateway.Cl return nil } + +func createModel(ctx context.Context, logger *slog.Logger, client *apigateway.Client, restAPIID string) error { + modelName := "testModel" + + // check if a model with the same testID already exists + err := findModelByName(ctx, client, restAPIID, modelName) + if err != nil { + if errors.As(err, new(integration.NotFoundError)) { + logger.InfoContext(ctx, "Creating model") + } else { + return err + } + } + + if err == nil { + logger.InfoContext(ctx, "Model already exists") + return nil + } + + _, err = client.CreateModel(ctx, &apigateway.CreateModelInput{ + RestApiId: &restAPIID, + Name: &modelName, + Schema: adapterhelpers.PtrString("{}"), + ContentType: adapterhelpers.PtrString("application/json"), + }) + if err != nil { + return err + } + + return nil +} diff --git a/adapters/integration/apigateway/find.go b/adapters/integration/apigateway/find.go index cde5bf39..3fa47681 100644 --- a/adapters/integration/apigateway/find.go +++ b/adapters/integration/apigateway/find.go @@ -184,10 +184,45 @@ func findStageByName(ctx context.Context, client *apigateway.Client, restAPIID, name, )) } + + return err + } + + if result == nil { + return integration.NewNotFoundError(integration.ResourceName( + integration.APIGateway, + stageSrc, + name, + )) + } + + return nil +} + +func findModelByName(ctx context.Context, client *apigateway.Client, restAPIID, name string) error { + result, err := client.GetModel(ctx, &apigateway.GetModelInput{ + RestApiId: &restAPIID, + ModelName: &name, + }) + if err != nil { + var notFoundErr *types.NotFoundException + if errors.As(err, ¬FoundErr) { + return integration.NewNotFoundError(integration.ResourceName( + integration.APIGateway, + stageSrc, + name, + )) + } + + return err } if result == nil { - return integration.NewNotFoundError(name) + return integration.NewNotFoundError(integration.ResourceName( + integration.APIGateway, + stageSrc, + name, + )) } return nil diff --git a/adapters/integration/apigateway/setup.go b/adapters/integration/apigateway/setup.go index 972ce0df..36a5a376 100644 --- a/adapters/integration/apigateway/setup.go +++ b/adapters/integration/apigateway/setup.go @@ -18,6 +18,7 @@ const ( authorizerSrc = "authorizer" deploymentSrc = "deployment" stageSrc = "stage" + modelSrc = "model" ) func setup(ctx context.Context, logger *slog.Logger, client *apigateway.Client) error { @@ -83,5 +84,11 @@ func setup(ctx context.Context, logger *slog.Logger, client *apigateway.Client) return err } + // Create Model + err = createModel(ctx, logger, client, *restApiID) + if err != nil { + return err + } + return nil } diff --git a/proc/proc.go b/proc/proc.go index 7c491262..79801e6c 100644 --- a/proc/proc.go +++ b/proc/proc.go @@ -487,6 +487,7 @@ func InitializeAwsSourceEngine(ctx context.Context, ec *discovery.EngineConfig, adapters.NewAPIGatewayAuthorizerAdapter(apigatewayClient, *callerID.Account, cfg.Region), adapters.NewAPIGatewayDeploymentAdapter(apigatewayClient, *callerID.Account, cfg.Region), adapters.NewAPIGatewayStageAdapter(apigatewayClient, *callerID.Account, cfg.Region), + adapters.NewAPIGatewayModelAdapter(apigatewayClient, *callerID.Account, cfg.Region), // SSM adapters.NewSSMParameterAdapter(ssmClient, *callerID.Account, cfg.Region), From e4a3f2e5ea775a2a503fb30df1871085c8a3e11e Mon Sep 17 00:00:00 2001 From: tomas Date: Tue, 21 Jan 2025 20:45:06 +0000 Subject: [PATCH 09/12] add terraform mappings to apigateway adapters --- adapters/apigateway-api-key.go | 3 +++ adapters/apigateway-authorizer.go | 3 +++ adapters/apigateway-deployment.go | 5 ++++- adapters/apigateway-domain-name.go | 2 +- adapters/apigateway-model.go | 3 +++ adapters/apigateway-stage.go | 3 +++ adapters/apigateway-vpc-link.go | 3 +++ 7 files changed, 20 insertions(+), 2 deletions(-) diff --git a/adapters/apigateway-api-key.go b/adapters/apigateway-api-key.go index 9519fcaf..53eeabf5 100644 --- a/adapters/apigateway-api-key.go +++ b/adapters/apigateway-api-key.go @@ -123,4 +123,7 @@ var apiKeyAdapterMetadata = Metadata.Register(&sdp.AdapterMetadata{ ListDescription: "List all API Keys", SearchDescription: "Search for API Keys by their name", }, + TerraformMappings: []*sdp.TerraformMapping{ + {TerraformQueryMap: "aws_api_gateway_api_key.id"}, + }, }) diff --git a/adapters/apigateway-authorizer.go b/adapters/apigateway-authorizer.go index a1bfc5d2..67786d3b 100644 --- a/adapters/apigateway-authorizer.go +++ b/adapters/apigateway-authorizer.go @@ -123,4 +123,7 @@ var authorizerAdapterMetadata = Metadata.Register(&sdp.AdapterMetadata{ GetDescription: "Get an API Gateway Authorizer by its rest API ID and ID: rest-api-id/authorizer-id", SearchDescription: "Search for API Gateway Authorizers by their rest API ID or with rest API ID and their name: rest-api-id/authorizer-name", }, + TerraformMappings: []*sdp.TerraformMapping{ + {TerraformQueryMap: "aws_api_gateway_authorizer.id"}, + }, }) diff --git a/adapters/apigateway-deployment.go b/adapters/apigateway-deployment.go index 20bbe92d..69ba9778 100644 --- a/adapters/apigateway-deployment.go +++ b/adapters/apigateway-deployment.go @@ -17,7 +17,7 @@ func convertGetDeploymentOutputToDeployment(output *apigateway.GetDeploymentOutp Id: output.Id, CreatedDate: output.CreatedDate, Description: output.Description, - ApiSummary: output.ApiSummary, + ApiSummary: output.ApiSummary, } } @@ -117,4 +117,7 @@ var deploymentAdapterMetadata = Metadata.Register(&sdp.AdapterMetadata{ GetDescription: "Get an API Gateway Deployment by its rest API ID and ID: rest-api-id/deployment-id", SearchDescription: "Search for API Gateway Deployments by their rest API ID or with rest API ID and their description: rest-api-id/deployment-description", }, + TerraformMappings: []*sdp.TerraformMapping{ + {TerraformQueryMap: "aws_api_gateway_deployment.id"}, + }, }) diff --git a/adapters/apigateway-domain-name.go b/adapters/apigateway-domain-name.go index 07ee88e2..b5dc2e85 100644 --- a/adapters/apigateway-domain-name.go +++ b/adapters/apigateway-domain-name.go @@ -235,6 +235,6 @@ var apiGatewayDomainNameAdapterMetadata = Metadata.Register(&sdp.AdapterMetadata }, PotentialLinks: []string{"acm-certificate"}, TerraformMappings: []*sdp.TerraformMapping{ - {TerraformQueryMap: "aws_api_gateway_domain_name.domain_name"}, + {TerraformQueryMap: "aws_api_gateway_domain_name.id"}, }, }) diff --git a/adapters/apigateway-model.go b/adapters/apigateway-model.go index 163be8b7..a2c83d18 100644 --- a/adapters/apigateway-model.go +++ b/adapters/apigateway-model.go @@ -111,4 +111,7 @@ var modelAdapterMetadata = Metadata.Register(&sdp.AdapterMetadata{ GetDescription: "Get an API Gateway Model by its rest API ID and model name: rest-api-id/model-name", SearchDescription: "Search for API Gateway Models by their rest API ID: rest-api-id", }, + TerraformMappings: []*sdp.TerraformMapping{ + {TerraformQueryMap: "aws_api_gateway_model.id"}, + }, }) diff --git a/adapters/apigateway-stage.go b/adapters/apigateway-stage.go index a2e420bc..334ef1f8 100644 --- a/adapters/apigateway-stage.go +++ b/adapters/apigateway-stage.go @@ -165,4 +165,7 @@ var stageAdapterMetadata = Metadata.Register(&sdp.AdapterMetadata{ SearchDescription: "Search for API Gateway Stages by their rest API ID or with rest API ID and deployment-id: rest-api-id/deployment-id", }, PotentialLinks: []string{"wafv2-web-acl"}, + TerraformMappings: []*sdp.TerraformMapping{ + {TerraformQueryMap: "aws_api_gateway_stage.id"}, + }, }) diff --git a/adapters/apigateway-vpc-link.go b/adapters/apigateway-vpc-link.go index 9425fa77..64d679d1 100644 --- a/adapters/apigateway-vpc-link.go +++ b/adapters/apigateway-vpc-link.go @@ -132,4 +132,7 @@ var vpcLinkAdapterMetadata = Metadata.Register(&sdp.AdapterMetadata{ ListDescription: "List all VPC Links", SearchDescription: "Search for VPC Links by their name", }, + TerraformMappings: []*sdp.TerraformMapping{ + {TerraformQueryMap: "aws_api_gateway_vpc_link.id"}, + }, }) From 2c7d7b6b63c67bb858e06ed1126eebcf0f1735cb Mon Sep 17 00:00:00 2001 From: tomas Date: Tue, 21 Jan 2025 20:59:32 +0000 Subject: [PATCH 10/12] add rest api relation to apigateway adapters --- adapters/apigateway-authorizer.go | 20 +++++++++++++++++--- adapters/apigateway-authorizer_test.go | 14 +++++++++++++- adapters/apigateway-deployment.go | 20 +++++++++++++++++--- adapters/apigateway-deployment_test.go | 14 +++++++++++++- adapters/apigateway-resource.go | 14 ++++++++++++++ adapters/apigateway-resource_test.go | 14 +++++++++++++- 6 files changed, 87 insertions(+), 9 deletions(-) diff --git a/adapters/apigateway-authorizer.go b/adapters/apigateway-authorizer.go index 67786d3b..c418c1e3 100644 --- a/adapters/apigateway-authorizer.go +++ b/adapters/apigateway-authorizer.go @@ -27,7 +27,7 @@ func convertGetAuthorizerOutputToAuthorizer(output *apigateway.GetAuthorizerOutp } } -func authorizerOutputMapper(scope string, awsItem *types.Authorizer) (*sdp.Item, error) { +func authorizerOutputMapper(query, scope string, awsItem *types.Authorizer) (*sdp.Item, error) { attributes, err := adapterhelpers.ToAttributesWithExclude(awsItem, "tags") if err != nil { return nil, err @@ -40,6 +40,20 @@ func authorizerOutputMapper(scope string, awsItem *types.Authorizer) (*sdp.Item, Scope: scope, } + item.LinkedItemQueries = append(item.LinkedItemQueries, &sdp.LinkedItemQuery{ + Query: &sdp.Query{ + Type: "apigateway-rest-api", + Method: sdp.QueryMethod_GET, + Query: strings.Split(query, "/")[0], + Scope: scope, + }, + BlastPropagation: &sdp.BlastPropagation{ + // They are tightly coupled, so we need to propagate the blast to the linked item + In: true, + Out: true, + }, + }) + return &item, nil } @@ -107,8 +121,8 @@ func NewAPIGatewayAuthorizerAdapter(client *apigateway.Client, accountID string, return items, nil }, - ItemMapper: func(_, scope string, awsItem *types.Authorizer) (*sdp.Item, error) { - return authorizerOutputMapper(scope, awsItem) + ItemMapper: func(query, scope string, awsItem *types.Authorizer) (*sdp.Item, error) { + return authorizerOutputMapper(query, scope, awsItem) }, } } diff --git a/adapters/apigateway-authorizer_test.go b/adapters/apigateway-authorizer_test.go index aa5ffee9..579d7202 100644 --- a/adapters/apigateway-authorizer_test.go +++ b/adapters/apigateway-authorizer_test.go @@ -1,6 +1,7 @@ package adapters import ( + "github.com/overmindtech/sdp-go" "testing" "time" @@ -24,7 +25,7 @@ func TestAuthorizerOutputMapper(t *testing.T) { AuthorizerResultTtlInSeconds: aws.Int32(300), } - item, err := authorizerOutputMapper("scope", awsItem) + item, err := authorizerOutputMapper("rest-api-id", "scope", awsItem) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -32,6 +33,17 @@ func TestAuthorizerOutputMapper(t *testing.T) { if err := item.Validate(); err != nil { t.Error(err) } + + tests := adapterhelpers.QueryTests{ + { + ExpectedType: "apigateway-rest-api", + ExpectedMethod: sdp.QueryMethod_GET, + ExpectedQuery: "rest-api-id", + ExpectedScope: "scope", + }, + } + + tests.Execute(t, item) } func TestNewAPIGatewayAuthorizerAdapter(t *testing.T) { diff --git a/adapters/apigateway-deployment.go b/adapters/apigateway-deployment.go index 69ba9778..2b8e5008 100644 --- a/adapters/apigateway-deployment.go +++ b/adapters/apigateway-deployment.go @@ -21,7 +21,7 @@ func convertGetDeploymentOutputToDeployment(output *apigateway.GetDeploymentOutp } } -func deploymentOutputMapper(scope string, awsItem *types.Deployment) (*sdp.Item, error) { +func deploymentOutputMapper(query, scope string, awsItem *types.Deployment) (*sdp.Item, error) { attributes, err := adapterhelpers.ToAttributesWithExclude(awsItem, "tags") if err != nil { return nil, err @@ -34,6 +34,20 @@ func deploymentOutputMapper(scope string, awsItem *types.Deployment) (*sdp.Item, Scope: scope, } + item.LinkedItemQueries = append(item.LinkedItemQueries, &sdp.LinkedItemQuery{ + Query: &sdp.Query{ + Type: "apigateway-rest-api", + Method: sdp.QueryMethod_GET, + Query: strings.Split(query, "/")[0], + Scope: scope, + }, + BlastPropagation: &sdp.BlastPropagation{ + // They are tightly coupled, so we need to propagate the blast to the linked item + In: true, + Out: true, + }, + }) + return &item, nil } @@ -101,8 +115,8 @@ func NewAPIGatewayDeploymentAdapter(client *apigateway.Client, accountID string, return items, nil }, - ItemMapper: func(_, scope string, awsItem *types.Deployment) (*sdp.Item, error) { - return deploymentOutputMapper(scope, awsItem) + ItemMapper: func(query, scope string, awsItem *types.Deployment) (*sdp.Item, error) { + return deploymentOutputMapper(query, scope, awsItem) }, } } diff --git a/adapters/apigateway-deployment_test.go b/adapters/apigateway-deployment_test.go index fb6da910..49a585ed 100644 --- a/adapters/apigateway-deployment_test.go +++ b/adapters/apigateway-deployment_test.go @@ -1,6 +1,7 @@ package adapters import ( + "github.com/overmindtech/sdp-go" "testing" "time" @@ -18,7 +19,7 @@ func TestDeploymentOutputMapper(t *testing.T) { ApiSummary: map[string]map[string]types.MethodSnapshot{}, } - item, err := deploymentOutputMapper("scope", awsItem) + item, err := deploymentOutputMapper("rest-api-id", "scope", awsItem) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -26,6 +27,17 @@ func TestDeploymentOutputMapper(t *testing.T) { if err := item.Validate(); err != nil { t.Error(err) } + + tests := adapterhelpers.QueryTests{ + { + ExpectedType: "apigateway-rest-api", + ExpectedMethod: sdp.QueryMethod_GET, + ExpectedQuery: "rest-api-id", + ExpectedScope: "scope", + }, + } + + tests.Execute(t, item) } func TestNewAPIGatewayDeploymentAdapter(t *testing.T) { diff --git a/adapters/apigateway-resource.go b/adapters/apigateway-resource.go index 43f56994..a37fc1ec 100644 --- a/adapters/apigateway-resource.go +++ b/adapters/apigateway-resource.go @@ -73,6 +73,20 @@ func resourceOutputMapper(query, scope string, awsItem *types.Resource) (*sdp.It } } + item.LinkedItemQueries = append(item.LinkedItemQueries, &sdp.LinkedItemQuery{ + Query: &sdp.Query{ + Type: "apigateway-rest-api", + Method: sdp.QueryMethod_GET, + Query: restApiID, + Scope: scope, + }, + BlastPropagation: &sdp.BlastPropagation{ + // They are tightly coupled, so we need to propagate the blast to the linked item + In: true, + Out: true, + }, + }) + return &item, nil } diff --git a/adapters/apigateway-resource_test.go b/adapters/apigateway-resource_test.go index e95e34c8..5aca6b93 100644 --- a/adapters/apigateway-resource_test.go +++ b/adapters/apigateway-resource_test.go @@ -1,6 +1,7 @@ package adapters import ( + "github.com/overmindtech/sdp-go" "testing" "time" @@ -152,7 +153,7 @@ func TestResourceOutputMapper(t *testing.T) { }, } - item, err := resourceOutputMapper("rest-api-13", "scope", resource) + item, err := resourceOutputMapper("rest-api-id", "scope", resource) if err != nil { t.Fatal(err) } @@ -160,6 +161,17 @@ func TestResourceOutputMapper(t *testing.T) { if err := item.Validate(); err != nil { t.Error(err) } + + tests := adapterhelpers.QueryTests{ + { + ExpectedType: "apigateway-rest-api", + ExpectedMethod: sdp.QueryMethod_GET, + ExpectedQuery: "rest-api-id", + ExpectedScope: "scope", + }, + } + + tests.Execute(t, item) } func TestNewAPIGatewayResourceAdapter(t *testing.T) { From a7ae8c4ea014acfc2b28778c4533c2381d3ed6d9 Mon Sep 17 00:00:00 2001 From: tomas Date: Thu, 23 Jan 2025 00:04:33 +0000 Subject: [PATCH 11/12] add apigateway base path mapping --- adapters/apigateway-base-path-mapping.go | 129 ++++++++++++++++++ adapters/apigateway-base-path-mapping_test.go | 62 +++++++++ proc/proc.go | 1 + 3 files changed, 192 insertions(+) create mode 100644 adapters/apigateway-base-path-mapping.go create mode 100644 adapters/apigateway-base-path-mapping_test.go diff --git a/adapters/apigateway-base-path-mapping.go b/adapters/apigateway-base-path-mapping.go new file mode 100644 index 00000000..b73eb319 --- /dev/null +++ b/adapters/apigateway-base-path-mapping.go @@ -0,0 +1,129 @@ +package adapters + +import ( + "context" + "fmt" + "strings" + + "github.com/aws/aws-sdk-go-v2/service/apigateway" + "github.com/aws/aws-sdk-go-v2/service/apigateway/types" + "github.com/overmindtech/aws-source/adapterhelpers" + "github.com/overmindtech/sdp-go" +) + +func convertGetBasePathMappingOutputToBasePathMapping(output *apigateway.GetBasePathMappingOutput) *types.BasePathMapping { + return &types.BasePathMapping{ + BasePath: output.BasePath, + RestApiId: output.RestApiId, + Stage: output.Stage, + } +} + +func basePathMappingOutputMapper(query, scope string, awsItem *types.BasePathMapping) (*sdp.Item, error) { + attributes, err := adapterhelpers.ToAttributesWithExclude(awsItem, "tags") + if err != nil { + return nil, err + } + + domainName := strings.Split(query, "/")[0] + + err = attributes.Set("UniqueAttribute", fmt.Sprintf("%s/%s", domainName, *awsItem.BasePath)) + + item := sdp.Item{ + Type: "apigateway-base-path-mapping", + UniqueAttribute: "BasePath", + Attributes: attributes, + Scope: scope, + } + + item.LinkedItemQueries = append(item.LinkedItemQueries, &sdp.LinkedItemQuery{ + Query: &sdp.Query{ + Type: "apigateway-domain-name", + Method: sdp.QueryMethod_GET, + Query: domainName, + Scope: scope, + }, + BlastPropagation: &sdp.BlastPropagation{ + // If domain name changes, the base path mapping will be affected + In: true, + // If base path mapping changes, the domain name won't be affected + Out: false, + }, + }) + + if awsItem.RestApiId != nil { + item.LinkedItemQueries = append(item.LinkedItemQueries, &sdp.LinkedItemQuery{ + Query: &sdp.Query{ + Type: "apigateway-rest-api", + Method: sdp.QueryMethod_GET, + Query: *awsItem.RestApiId, + Scope: scope, + }, + BlastPropagation: &sdp.BlastPropagation{ + // They are tightly coupled, so we need to propagate the blast to the linked item + In: true, + Out: true, + }, + }) + } + + return &item, nil +} + +func NewAPIGatewayBasePathMappingAdapter(client *apigateway.Client, accountID string, region string) *adapterhelpers.GetListAdapter[*types.BasePathMapping, *apigateway.Client, *apigateway.Options] { + return &adapterhelpers.GetListAdapter[*types.BasePathMapping, *apigateway.Client, *apigateway.Options]{ + ItemType: "apigateway-base-path-mapping", + Client: client, + AccountID: accountID, + Region: region, + AdapterMetadata: basePathMappingAdapterMetadata, + GetFunc: func(ctx context.Context, client *apigateway.Client, scope, query string) (*types.BasePathMapping, error) { + f := strings.Split(query, "/") + if len(f) != 2 { + return nil, &sdp.QueryError{ + ErrorType: sdp.QueryError_NOTFOUND, + ErrorString: fmt.Sprintf("query must be in the format of: the domain-name/base-path, but found: %s", query), + } + } + out, err := client.GetBasePathMapping(ctx, &apigateway.GetBasePathMappingInput{ + DomainName: &f[0], + BasePath: &f[1], + }) + if err != nil { + return nil, err + } + return convertGetBasePathMappingOutputToBasePathMapping(out), nil + }, + DisableList: true, + SearchFunc: func(ctx context.Context, client *apigateway.Client, scope string, query string) ([]*types.BasePathMapping, error) { + out, err := client.GetBasePathMappings(ctx, &apigateway.GetBasePathMappingsInput{ + DomainName: &query, + }) + if err != nil { + return nil, err + } + + var items []*types.BasePathMapping + for _, basePathMapping := range out.Items { + items = append(items, &basePathMapping) + } + + return items, nil + }, + ItemMapper: func(query, scope string, awsItem *types.BasePathMapping) (*sdp.Item, error) { + return basePathMappingOutputMapper(query, scope, awsItem) + }, + } +} + +var basePathMappingAdapterMetadata = Metadata.Register(&sdp.AdapterMetadata{ + Type: "apigateway-base-path-mapping", + DescriptiveName: "API Gateway Base Path Mapping", + Category: sdp.AdapterCategory_ADAPTER_CATEGORY_CONFIGURATION, + SupportedQueryMethods: &sdp.AdapterSupportedQueryMethods{ + Get: true, + Search: true, + GetDescription: "Get an API Gateway Base Path Mapping by its domain name and base path: domain-name/base-path", + SearchDescription: "Search for API Gateway Base Path Mappings by their domain name: domain-name", + }, +}) diff --git a/adapters/apigateway-base-path-mapping_test.go b/adapters/apigateway-base-path-mapping_test.go new file mode 100644 index 00000000..d188bf07 --- /dev/null +++ b/adapters/apigateway-base-path-mapping_test.go @@ -0,0 +1,62 @@ +package adapters + +import ( + "github.com/overmindtech/sdp-go" + "testing" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/apigateway" + "github.com/aws/aws-sdk-go-v2/service/apigateway/types" + "github.com/overmindtech/aws-source/adapterhelpers" +) + +func TestBasePathMappingOutputMapper(t *testing.T) { + awsItem := &types.BasePathMapping{ + BasePath: aws.String("base-path"), + RestApiId: aws.String("rest-api-id"), + Stage: aws.String("stage"), + } + + item, err := basePathMappingOutputMapper("domain-name", "scope", awsItem) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if err := item.Validate(); err != nil { + t.Error(err) + } + + tests := adapterhelpers.QueryTests{ + { + ExpectedType: "apigateway-domain-name", + ExpectedMethod: sdp.QueryMethod_GET, + ExpectedQuery: "domain-name", + ExpectedScope: "scope", + }, + { + ExpectedType: "apigateway-rest-api", + ExpectedMethod: sdp.QueryMethod_GET, + ExpectedQuery: "rest-api-id", + ExpectedScope: "scope", + }, + } + + tests.Execute(t, item) +} + +func TestNewAPIGatewayBasePathMappingAdapter(t *testing.T) { + config, account, region := adapterhelpers.GetAutoConfig(t) + + client := apigateway.NewFromConfig(config) + + adapter := NewAPIGatewayBasePathMappingAdapter(client, account, region) + + test := adapterhelpers.E2ETest{ + Adapter: adapter, + Timeout: 10 * time.Second, + SkipList: true, + } + + test.Run(t) +} diff --git a/proc/proc.go b/proc/proc.go index 79801e6c..e490ef87 100644 --- a/proc/proc.go +++ b/proc/proc.go @@ -488,6 +488,7 @@ func InitializeAwsSourceEngine(ctx context.Context, ec *discovery.EngineConfig, adapters.NewAPIGatewayDeploymentAdapter(apigatewayClient, *callerID.Account, cfg.Region), adapters.NewAPIGatewayStageAdapter(apigatewayClient, *callerID.Account, cfg.Region), adapters.NewAPIGatewayModelAdapter(apigatewayClient, *callerID.Account, cfg.Region), + adapters.NewAPIGatewayBasePathMappingAdapter(apigatewayClient, *callerID.Account, cfg.Region), // SSM adapters.NewSSMParameterAdapter(ssmClient, *callerID.Account, cfg.Region), From 40d724cf3bb33d9ed451399a87b3af6918f720e8 Mon Sep 17 00:00:00 2001 From: Dylan Ratcliffe Date: Thu, 23 Jan 2025 22:14:46 +0000 Subject: [PATCH 12/12] Remove commit file --- tracing/commit.txt | 1 - 1 file changed, 1 deletion(-) delete mode 100644 tracing/commit.txt diff --git a/tracing/commit.txt b/tracing/commit.txt deleted file mode 100644 index c8ca9b98..00000000 --- a/tracing/commit.txt +++ /dev/null @@ -1 +0,0 @@ -6dad160 \ No newline at end of file