From 98e87a219d65212fc7ff5e5984137ca834bced43 Mon Sep 17 00:00:00 2001 From: Saqib Dhuka Date: Fri, 1 Oct 2021 21:19:58 +0000 Subject: [PATCH] Added StepFunctionsStartExecutionIntegration and StepFunctionsStartSyncExecutionIntegration. Added unit and integration tests. Fixed issues mentioned in the Pull-request. Added example to README closes #11947. --- .../aws-apigatewayv2-integrations/README.md | 46 ++++ .../lib/http/aws.ts | 168 +++++++++++++ .../lib/http/base-types.ts | 21 ++ .../lib/http/index.ts | 1 + .../lib/http/private/integration.ts | 64 +++++ .../package.json | 7 +- .../test/http/aws.test.ts | 95 ++++++++ ...s-integration-startExecution.expected.json | 196 +++++++++++++++ .../integ.aws-integration-startExecution.ts | 29 +++ ...tegration-startSyncExecution.expected.json | 227 ++++++++++++++++++ ...nteg.aws-integration-startSyncExecution.ts | 29 +++ .../@aws-cdk/aws-apigatewayv2/lib/http/api.ts | 5 + .../aws-apigatewayv2/lib/http/integration.ts | 170 +++++++++---- 13 files changed, 1014 insertions(+), 44 deletions(-) create mode 100644 packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/aws.ts create mode 100644 packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/aws.test.ts create mode 100644 packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.aws-integration-startExecution.expected.json create mode 100644 packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.aws-integration-startExecution.ts create mode 100644 packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.aws-integration-startSyncExecution.expected.json create mode 100644 packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.aws-integration-startSyncExecution.ts diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md b/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md index cce77fd6398e6..8a366780d8a68 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md @@ -20,6 +20,7 @@ - [HTTP APIs](#http-apis) - [Lambda Integration](#lambda) - [HTTP Proxy Integration](#http-proxy) + - [AWS Service Integration](#aws-service) - [Private Integration](#private-integration) - [WebSocket APIs](#websocket-apis) - [Lambda WebSocket Integration](#lambda-websocket-integration) @@ -78,6 +79,51 @@ httpApi.addRoutes({ }); ``` +### AWS Service + +You can integrate your HTTP API with AWS services by using first-class integrations. A first-class integration connects +an HTTP API route to an AWS service API. When a client invokes a route that's backed by a first-class integration, +API Gateway invokes an AWS service API for you. More information can be found at [Working with AWS service integrations for HTTP APIs] +(https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-aws-services.html). + +The following code configures a route `POST /start` to start execution of Standard Step Functions state machine. + +```ts +const httpApi = new HttpApi(stack, 'HttpApi'); +const state = new StateMachine(stack, 'MyStateMachine', { + definition: Chain.start(new Pass(stack, 'Pass')), + stateMachineType: StateMachineType.STANDARD, +}); +httpApi.addRoutes({ + path: '/start', + methods: [ HttpMethod.POST ], + integration: new StepFunctionsStartExecutionIntegration({ + stateMachine: state, + input: '$request.body', + timeout: Duration.seconds(10), + }), +}); +``` + +The following code configures a route `POST /start` to start synchronous execution of Express Step Functions state machine. + +```ts +const httpApi = new HttpApi(stack, 'HttpApi'); +const state = new StateMachine(stack, 'MyStateMachine', { + definition: Chain.start(new Pass(stack, 'Pass')), + stateMachineType: StateMachineType.EXPRESS, +}); +httpApi.addRoutes({ + path: '/start', + methods: [ HttpMethod.POST ], + integration: new StepFunctionsStartSyncExecutionIntegration({ + stateMachine: state, + input: '$request.body', + timeout: Duration.seconds(10), + }), +}); +``` + ### Private Integration Private integrations enable integrating an HTTP API route with private resources in a VPC, such as Application Load Balancers or diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/aws.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/aws.ts new file mode 100644 index 0000000000000..9ef0a6a1bb1c2 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/aws.ts @@ -0,0 +1,168 @@ +import * as iam from '@aws-cdk/aws-iam'; +import { IStateMachine } from '@aws-cdk/aws-stepfunctions'; +import { Construct } from 'constructs'; +import { AwsServiceIntegration, AwsServiceIntegrationProps } from './private/integration'; + +/** + * The common Step Functions integration resource for HTTP API + */ +abstract class StepFunctionsIntegration extends AwsServiceIntegration { + + /** + * + * @internal + */ + protected _integrationService(): string { + return 'StepFunctions'; + } +} + +/** + * Step Functions StartExecution integration properties + */ +export interface StepFunctionsStartExecutionIntegrationProps extends AwsServiceIntegrationProps { + + /** + * The state machine to be executed + */ + readonly stateMachine: IStateMachine; + + /** + * The execution name + * + * @default - undefined + */ + readonly name?: string; + + /** + * The input parameters of execution + * + * @default - undefined + */ + readonly input?: any; + + /** + * The region of state machine + * + * @default - undefined + */ + readonly region?: string; + + /** + * Passes the AWS X-Ray trace header. The trace header can also be passed in the request payload. + * + * @default - undefined + */ + readonly traceHeader?: string; +} + +/** + * The StepFunctions-StartExecution integration resource for HTTP API + */ +export class StepFunctionsStartExecutionIntegration extends StepFunctionsIntegration { + + constructor(private readonly _scope: Construct, private readonly _props: StepFunctionsStartExecutionIntegrationProps) { + super(_props); + } + + /** + * + * @internal + */ + protected _integrationAction(): string { + return 'StartExecution'; + } + + /** + * + * @internal + */ + protected _fulfillRole(credentialsRole: iam.IRole): void { + this._props.stateMachine.grantStartExecution(credentialsRole); + credentialsRole.attachInlinePolicy( + new iam.Policy(this._scope, 'AllowSfnExec', { + statements: [ + new iam.PolicyStatement({ + actions: ['states:StartExecution'], + effect: iam.Effect.ALLOW, + resources: [this._props.stateMachine.stateMachineArn], + }), + ], + }), + ); + + } + + /** + * + * @internal + */ + protected _buildRequestParameters(): { [key: string]: any } { + return { + StateMachineArn: this._props.stateMachine.stateMachineArn, + Name: this._props.name, + Input: this._props.input, + Region: this._props.region, + TraceHeader: this._props.traceHeader, + }; + } +} + +/** + * Step Functions StartSyncExecution integration properties + */ +export interface StepFunctionsStartSyncExecutionIntegrationProps extends StepFunctionsStartExecutionIntegrationProps { +} + +/** + * The StepFunctions-StartExecution integration resource for HTTP API + */ +export class StepFunctionsStartSyncExecutionIntegration extends StepFunctionsIntegration { + + constructor(private readonly _scope: Construct, private readonly _props: StepFunctionsStartSyncExecutionIntegrationProps) { + super(_props); + } + + /** + * + * @internal + */ + protected _integrationAction(): string { + return 'StartSyncExecution'; + } + + /** + * + * @internal + */ + protected _fulfillRole(credentialsRole: iam.IRole): void { + + this._props.stateMachine.grantExecution(credentialsRole.grantPrincipal, 'states:StartSyncExecution'); + credentialsRole.attachInlinePolicy( + new iam.Policy(this._scope, 'AllowSfnSyncExec', { + statements: [ + new iam.PolicyStatement({ + actions: ['states:StartSyncExecution'], + effect: iam.Effect.ALLOW, + resources: [this._props.stateMachine.stateMachineArn], + }), + ], + }), + ); + + } + + /** + * + * @internal + */ + protected _buildRequestParameters(): { [key: string]: any } { + return { + StateMachineArn: this._props.stateMachine.stateMachineArn, + Name: this._props.name, + Input: this._props.input, + Region: this._props.region, + TraceHeader: this._props.traceHeader, + }; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/base-types.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/base-types.ts index db14e50f7fc54..a87f71c3ddd46 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/base-types.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/base-types.ts @@ -1,4 +1,5 @@ import { HttpMethod, IVpcLink } from '@aws-cdk/aws-apigatewayv2'; +import { Duration } from '@aws-cdk/core'; /** * Base options for private integration @@ -25,3 +26,23 @@ export interface HttpPrivateIntegrationOptions { readonly secureServerName?: string; } + +/** + * Common properties to initialize a new `HttpProxyIntegration`. + */ +export interface CommonIntegrationProps { + + /** + * The description of the integration + * + * @default - undefined + */ + readonly description?: string; + + /** + * Custom timeout for HTTP APIs + * + * @default - undefined + */ + readonly timeout?: Duration; +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/index.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/index.ts index 8e0598975f8cb..9bb5a46315f63 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/index.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/index.ts @@ -4,3 +4,4 @@ export * from './nlb'; export * from './service-discovery'; export * from './http-proxy'; export * from './lambda'; +export * from './aws'; diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/private/integration.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/private/integration.ts index 6d32b22794722..62e8ae75f9c6e 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/private/integration.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/private/integration.ts @@ -9,6 +9,8 @@ import { IVpcLink, } from '@aws-cdk/aws-apigatewayv2'; import * as ec2 from '@aws-cdk/aws-ec2'; +import { IRole, Role, ServicePrincipal } from '@aws-cdk/aws-iam'; +import { CommonIntegrationProps } from '../base-types'; /** @@ -63,3 +65,65 @@ export abstract class HttpPrivateIntegration implements IHttpRouteIntegration { public abstract bind(options: HttpRouteIntegrationBindOptions): HttpRouteIntegrationConfig; } + +/** + * Aws Service integration properties + * + * @internal + */ +export interface AwsServiceIntegrationProps extends CommonIntegrationProps { +} + +/** + * The Aws Service integration resource for HTTP API + * + * @internal + */ +export abstract class AwsServiceIntegration implements IHttpRouteIntegration { + + constructor(private readonly props: AwsServiceIntegrationProps) { + } + + /** + * + * @internal + */ + protected abstract _fulfillRole(credentialsRole: IRole): void; + + /** + * + * @internal + */ + protected abstract _buildRequestParameters(): { [key: string]: any }; + + /** + * + * @internal + */ + protected abstract _integrationService(): string; + + /** + * + * @internal + */ + protected abstract _integrationAction(): string; + + public bind(_options: HttpRouteIntegrationBindOptions): HttpRouteIntegrationConfig { + + const apiGatewayIntegrationRole = new Role(_options.scope, 'ApiGatewayIntegrationRole', { + assumedBy: new ServicePrincipal('apigateway.amazonaws.com'), + }); + this._fulfillRole(apiGatewayIntegrationRole); + + return { + payloadFormatVersion: PayloadFormatVersion.VERSION_1_0, // 1.0 is required and is the only supported format + type: HttpIntegrationType.LAMBDA_PROXY, + subtype: `${this._integrationService()}-${this._integrationAction()}`, + credentials: apiGatewayIntegrationRole.roleArn, + timeout: this.props.timeout, + description: this.props.description, + requestParameters: this._buildRequestParameters(), + }; + } + +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/package.json b/packages/@aws-cdk/aws-apigatewayv2-integrations/package.json index a950d542429e0..fdb05b46bf601 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/package.json +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/package.json @@ -73,8 +73,11 @@ "@aws-cdk/assertions": "0.0.0", "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", + "@aws-cdk/assert-internal": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^26.0.24", + "@types/nodeunit": "^0.0.31", + "nodeunit": "^0.11.3" }, "dependencies": { "@aws-cdk/aws-apigatewayv2": "0.0.0", @@ -83,6 +86,7 @@ "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/aws-servicediscovery": "0.0.0", + "@aws-cdk/aws-stepfunctions": "0.0.0", "@aws-cdk/core": "0.0.0", "constructs": "^3.3.69" }, @@ -93,6 +97,7 @@ "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/aws-servicediscovery": "0.0.0", + "@aws-cdk/aws-stepfunctions": "0.0.0", "@aws-cdk/core": "0.0.0", "constructs": "^3.3.69" }, diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/aws.test.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/aws.test.ts new file mode 100644 index 0000000000000..6a602f540d9ed --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/aws.test.ts @@ -0,0 +1,95 @@ +import '@aws-cdk/assert-internal/jest'; +import { HttpApi, HttpRoute, HttpRouteKey } from '@aws-cdk/aws-apigatewayv2'; +import { StateMachine, Chain, Pass, StateMachineType } from '@aws-cdk/aws-stepfunctions'; +import { Stack, Duration } from '@aws-cdk/core'; +import { StepFunctionsStartExecutionIntegration, StepFunctionsStartSyncExecutionIntegration } from '../../lib'; + +describe('AwsServiceIntegration', () => { + test('StepFunctions-StartExecution', () => { + const stack = new Stack(); + const api = new HttpApi(stack, 'HttpApi'); + + new HttpRoute(stack, 'StepFunctionsStartExeRoute', { + httpApi: api, + integration: new StepFunctionsStartExecutionIntegration(stack, { + stateMachine: stateMachine(stack), + name: 'MyExe', + input: '$request.body', + timeout: Duration.seconds(10), + description: 'Start execution of state machine', + }), + routeKey: HttpRouteKey.with('/start'), + }); + + expect(stack).toHaveResource('AWS::ApiGatewayV2::Integration', { + ApiId: { + Ref: 'HttpApiF5A9A8A7', + }, + IntegrationType: 'AWS_PROXY', + CredentialsArn: { + 'Fn::GetAtt': [ + 'StepFunctionsStartExeRouteApiGatewayIntegrationRoleE88875A9', + 'Arn', + ], + }, + Description: 'Start execution of state machine', + IntegrationSubtype: 'StepFunctions-StartExecution', + PayloadFormatVersion: '1.0', + RequestParameters: { + StateMachineArn: { + Ref: 'MyStateMachine6C968CA5', + }, + Input: '$request.body', + Name: 'MyExe', + }, + TimeoutInMillis: 10000, + }); + }); + + test('StepFunctions-StartSyncExecution', () => { + const stack = new Stack(); + const api = new HttpApi(stack, 'HttpApi'); + + new HttpRoute(stack, 'StepFunctionsStartSyncExeRoute', { + httpApi: api, + integration: new StepFunctionsStartSyncExecutionIntegration(stack, { + stateMachine: new StateMachine(stack, 'MyStateMachine', { + stateMachineName: 'MyStateMachine', + definition: Chain.start(new Pass(stack, 'Pass')), + stateMachineType: StateMachineType.EXPRESS, + }), + input: '$request.body', + }), + routeKey: HttpRouteKey.with('/startSync'), + }); + + expect(stack).toHaveResource('AWS::ApiGatewayV2::Integration', { + ApiId: { + Ref: 'HttpApiF5A9A8A7', + }, + IntegrationType: 'AWS_PROXY', + CredentialsArn: { + 'Fn::GetAtt': [ + 'StepFunctionsStartSyncExeRouteApiGatewayIntegrationRole67241EA0', + 'Arn', + ], + }, + IntegrationSubtype: 'StepFunctions-StartSyncExecution', + PayloadFormatVersion: '1.0', + RequestParameters: { + StateMachineArn: { + Ref: 'MyStateMachine6C968CA5', + }, + Input: '$request.body', + }, + }); + }); +}); + +function stateMachine(stack: Stack): StateMachine { + return new StateMachine(stack, 'MyStateMachine', { + stateMachineName: 'MyStateMachine', + definition: Chain.start(new Pass(stack, 'Pass')), + stateMachineType: StateMachineType.STANDARD, + }); +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.aws-integration-startExecution.expected.json b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.aws-integration-startExecution.expected.json new file mode 100644 index 0000000000000..9db2e8916d4c7 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.aws-integration-startExecution.expected.json @@ -0,0 +1,196 @@ +{ + "Resources": { + "MyStateMachineRoleD59FFEBC": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "states.", + { + "Ref": "AWS::Region" + }, + ".amazonaws.com" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "MyStateMachine6C968CA5": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "MyStateMachineRoleD59FFEBC", + "Arn" + ] + }, + "DefinitionString": "{\"StartAt\":\"Pass\",\"States\":{\"Pass\":{\"Type\":\"Pass\",\"End\":true}}}", + "StateMachineType": "STANDARD" + }, + "DependsOn": [ + "MyStateMachineRoleD59FFEBC" + ] + }, + "AwsIntegrationApi1AEE0491": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Name": "AwsIntegrationApi", + "ProtocolType": "HTTP" + } + }, + "AwsIntegrationApiDefaultRouteApiGatewayIntegrationRole57718B44": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "apigateway.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "AwsIntegrationApiDefaultRouteApiGatewayIntegrationRoleDefaultPolicy21C90B92": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Effect": "Allow", + "Resource": { + "Ref": "MyStateMachine6C968CA5" + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "AwsIntegrationApiDefaultRouteApiGatewayIntegrationRoleDefaultPolicy21C90B92", + "Roles": [ + { + "Ref": "AwsIntegrationApiDefaultRouteApiGatewayIntegrationRole57718B44" + } + ] + } + }, + "AwsIntegrationApiDefaultRouteHttpIntegration2f0cb9144d46bb679d933c010c9875266F1CBFB8": { + "Type": "AWS::ApiGatewayV2::Integration", + "Properties": { + "ApiId": { + "Ref": "AwsIntegrationApi1AEE0491" + }, + "IntegrationType": "AWS_PROXY", + "CredentialsArn": { + "Fn::GetAtt": [ + "AwsIntegrationApiDefaultRouteApiGatewayIntegrationRole57718B44", + "Arn" + ] + }, + "IntegrationSubtype": "StepFunctions-StartExecution", + "PayloadFormatVersion": "1.0", + "RequestParameters": { + "StateMachineArn": { + "Ref": "MyStateMachine6C968CA5" + }, + "Input": "$request.body" + } + } + }, + "AwsIntegrationApiDefaultRouteF019925B": { + "Type": "AWS::ApiGatewayV2::Route", + "Properties": { + "ApiId": { + "Ref": "AwsIntegrationApi1AEE0491" + }, + "RouteKey": "$default", + "AuthorizationType": "NONE", + "Target": { + "Fn::Join": [ + "", + [ + "integrations/", + { + "Ref": "AwsIntegrationApiDefaultRouteHttpIntegration2f0cb9144d46bb679d933c010c9875266F1CBFB8" + } + ] + ] + } + } + }, + "AwsIntegrationApiDefaultStageDFAEF224": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "AwsIntegrationApi1AEE0491" + }, + "StageName": "$default", + "AutoDeploy": true + } + }, + "AllowSfnExecFA6BC8D6": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Effect": "Allow", + "Resource": { + "Ref": "MyStateMachine6C968CA5" + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "AllowSfnExecFA6BC8D6", + "Roles": [ + { + "Ref": "AwsIntegrationApiDefaultRouteApiGatewayIntegrationRole57718B44" + } + ] + } + } + }, + "Outputs": { + "Endpoint": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "AwsIntegrationApi1AEE0491" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/" + ] + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.aws-integration-startExecution.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.aws-integration-startExecution.ts new file mode 100644 index 0000000000000..9dbd7ac1c94c3 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.aws-integration-startExecution.ts @@ -0,0 +1,29 @@ +import { HttpApi } from '@aws-cdk/aws-apigatewayv2'; +import { StateMachine, Chain, StateMachineType, Pass } from '@aws-cdk/aws-stepfunctions'; +import { App, CfnOutput, Stack } from '@aws-cdk/core'; +import { StepFunctionsStartExecutionIntegration } from '../../lib'; + +/* + * Stack verification steps: + * "curl " should return 'success' + */ + +const app = new App(); + +const stack = new Stack(app, 'integ-aws-service'); + +const state = new StateMachine(stack, 'MyStateMachine', { + definition: Chain.start(new Pass(stack, 'Pass')), + stateMachineType: StateMachineType.STANDARD, +}); + +const endpoint = new HttpApi(stack, 'AwsIntegrationApi', { + defaultIntegration: new StepFunctionsStartExecutionIntegration(stack, { + stateMachine: state, + input: '$request.body', + }), +}); + +new CfnOutput(stack, 'Endpoint', { + value: endpoint.url!, +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.aws-integration-startSyncExecution.expected.json b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.aws-integration-startSyncExecution.expected.json new file mode 100644 index 0000000000000..d682a8765061a --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.aws-integration-startSyncExecution.expected.json @@ -0,0 +1,227 @@ +{ + "Resources": { + "MyStateMachineRoleD59FFEBC": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "states.", + { + "Ref": "AWS::Region" + }, + ".amazonaws.com" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "MyStateMachine6C968CA5": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "MyStateMachineRoleD59FFEBC", + "Arn" + ] + }, + "DefinitionString": "{\"StartAt\":\"Pass\",\"States\":{\"Pass\":{\"Type\":\"Pass\",\"End\":true}}}", + "StateMachineType": "EXPRESS" + }, + "DependsOn": [ + "MyStateMachineRoleD59FFEBC" + ] + }, + "AwsIntegrationApi1AEE0491": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Name": "AwsIntegrationApi", + "ProtocolType": "HTTP" + } + }, + "AwsIntegrationApiDefaultRouteApiGatewayIntegrationRole57718B44": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "apigateway.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "AwsIntegrationApiDefaultRouteApiGatewayIntegrationRoleDefaultPolicy21C90B92": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartSyncExecution", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":states:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":execution:", + { + "Fn::Select": [ + 6, + { + "Fn::Split": [ + ":", + { + "Ref": "MyStateMachine6C968CA5" + } + ] + } + ] + }, + ":*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "AwsIntegrationApiDefaultRouteApiGatewayIntegrationRoleDefaultPolicy21C90B92", + "Roles": [ + { + "Ref": "AwsIntegrationApiDefaultRouteApiGatewayIntegrationRole57718B44" + } + ] + } + }, + "AwsIntegrationApiDefaultRouteHttpIntegration4c730d5cbb3beb8802425fa8380c19355D85A87E": { + "Type": "AWS::ApiGatewayV2::Integration", + "Properties": { + "ApiId": { + "Ref": "AwsIntegrationApi1AEE0491" + }, + "IntegrationType": "AWS_PROXY", + "CredentialsArn": { + "Fn::GetAtt": [ + "AwsIntegrationApiDefaultRouteApiGatewayIntegrationRole57718B44", + "Arn" + ] + }, + "IntegrationSubtype": "StepFunctions-StartSyncExecution", + "PayloadFormatVersion": "1.0", + "RequestParameters": { + "StateMachineArn": { + "Ref": "MyStateMachine6C968CA5" + }, + "Input": "$request.body" + } + } + }, + "AwsIntegrationApiDefaultRouteF019925B": { + "Type": "AWS::ApiGatewayV2::Route", + "Properties": { + "ApiId": { + "Ref": "AwsIntegrationApi1AEE0491" + }, + "RouteKey": "$default", + "AuthorizationType": "NONE", + "Target": { + "Fn::Join": [ + "", + [ + "integrations/", + { + "Ref": "AwsIntegrationApiDefaultRouteHttpIntegration4c730d5cbb3beb8802425fa8380c19355D85A87E" + } + ] + ] + } + } + }, + "AwsIntegrationApiDefaultStageDFAEF224": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "AwsIntegrationApi1AEE0491" + }, + "StageName": "$default", + "AutoDeploy": true + } + }, + "AllowSfnSyncExec72CF68FA": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartSyncExecution", + "Effect": "Allow", + "Resource": { + "Ref": "MyStateMachine6C968CA5" + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "AllowSfnSyncExec72CF68FA", + "Roles": [ + { + "Ref": "AwsIntegrationApiDefaultRouteApiGatewayIntegrationRole57718B44" + } + ] + } + } + }, + "Outputs": { + "Endpoint": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "AwsIntegrationApi1AEE0491" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/" + ] + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.aws-integration-startSyncExecution.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.aws-integration-startSyncExecution.ts new file mode 100644 index 0000000000000..5200bcc8ce94f --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.aws-integration-startSyncExecution.ts @@ -0,0 +1,29 @@ +import { HttpApi } from '@aws-cdk/aws-apigatewayv2'; +import { StateMachine, Chain, StateMachineType, Pass } from '@aws-cdk/aws-stepfunctions'; +import { App, CfnOutput, Stack } from '@aws-cdk/core'; +import { StepFunctionsStartSyncExecutionIntegration } from '../../lib'; + +/* + * Stack verification steps: + * "curl " should return 'success' + */ + +const app = new App(); + +const stack = new Stack(app, 'integ-aws-service'); + +const state = new StateMachine(stack, 'MyStateMachine', { + definition: Chain.start(new Pass(stack, 'Pass')), + stateMachineType: StateMachineType.EXPRESS, +}); + +const endpoint = new HttpApi(stack, 'AwsIntegrationApi', { + defaultIntegration: new StepFunctionsStartSyncExecutionIntegration(stack, { + stateMachine: state, + input: '$request.body', + }), +}); + +new CfnOutput(stack, 'Endpoint', { + value: endpoint.url!, +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts index 254a29ea6d28b..c854a2da7ec0e 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts @@ -298,12 +298,17 @@ abstract class HttpApiBase extends ApiBase implements IHttpApi { // note that th const integration = new HttpIntegration(scope, `HttpIntegration-${configHash}`, { httpApi: this, + description: config.description, integrationType: config.type, + integrationSubtype: config.subtype, integrationUri: config.uri, + requestParameters: config.requestParameters, + credentials: config.credentials, method: config.method, connectionId: config.connectionId, connectionType: config.connectionType, payloadFormatVersion: config.payloadFormatVersion, + timeout: config.timeout, secureServerName: config.secureServerName, }); this._integrationCache.saveIntegration(scope, config, integration); diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts index f832b5b7e3b21..d9b05c0e70e3a 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts @@ -1,5 +1,5 @@ /* eslint-disable quotes */ -import { Resource } from '@aws-cdk/core'; +import { Resource, Duration } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnIntegration } from '../apigatewayv2.generated'; import { IIntegration } from '../common'; @@ -84,42 +84,79 @@ export interface HttpIntegrationProps { readonly httpApi: IHttpApi; /** - * Integration type - */ + * The description of the integration + * + * @default - undefined + */ + readonly description?: string; + + /** + * Specifies the credentials ARN required for the integration, if any. + * + * @default - undefined + */ + readonly credentials?: string; + + /** + * Integration type + */ readonly integrationType: HttpIntegrationType; /** - * Integration URI. - * This will be the function ARN in the case of `HttpIntegrationType.LAMBDA_PROXY`, - * or HTTP URL in the case of `HttpIntegrationType.HTTP_PROXY`. - */ - readonly integrationUri: string; + * Specifies the AWS service action to invoke. + * + * @default - undefined + */ + readonly integrationSubtype?: string; /** - * The HTTP method to use when calling the underlying HTTP proxy - * @default - none. required if the integration type is `HttpIntegrationType.HTTP_PROXY`. - */ + * Custom timeout for HTTP APIs + * + * @default - undefined + */ + readonly timeout?: Duration; + + /** + * Request parameters are a key-value map specifying parameters that are passed to AWS_PROXY integrations. + * + * @default - undefined + */ + readonly requestParameters?: { [key: string]: any }; + + /** + * Integration URI. + * This will be the function ARN in the case of `HttpIntegrationType.LAMBDA_PROXY`, + * or HTTP URL in the case of `HttpIntegrationType.HTTP_PROXY`. + * + * @default - undefined + */ + readonly integrationUri?: string; + + /** + * The HTTP method to use when calling the underlying HTTP proxy + * @default - none. required if the integration type is `HttpIntegrationType.HTTP_PROXY`. + */ readonly method?: HttpMethod; /** - * The ID of the VPC link for a private integration. Supported only for HTTP APIs. - * - * @default - undefined - */ + * The ID of the VPC link for a private integration. Supported only for HTTP APIs. + * + * @default - undefined + */ readonly connectionId?: string; /** - * The type of the network connection to the integration endpoint - * - * @default HttpConnectionType.INTERNET - */ + * The type of the network connection to the integration endpoint + * + * @default HttpConnectionType.INTERNET + */ readonly connectionType?: HttpConnectionType; /** - * The version of the payload format - * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html - * @default - defaults to latest in the case of HttpIntegrationType.LAMBDA_PROXY`, irrelevant otherwise. - */ + * The version of the payload format + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html + * @default - defaults to latest in the case of HttpIntegrationType.LAMBDA_PROXY`, irrelevant otherwise. + */ readonly payloadFormatVersion?: PayloadFormatVersion; /** @@ -141,14 +178,24 @@ export class HttpIntegration extends Resource implements IHttpIntegration { constructor(scope: Construct, id: string, props: HttpIntegrationProps) { super(scope, id); + + if (props.timeout && (props.timeout.toMilliseconds() < 50 || props.timeout.toMilliseconds() > 30000)) { + throw new Error(`The timeout of HTTP integration should be between 50 and 30,000, got ${props.timeout.toMilliseconds()}.`); + } + const integ = new CfnIntegration(this, 'Resource', { apiId: props.httpApi.apiId, + description: props.description, integrationType: props.integrationType, + integrationSubtype: props.integrationSubtype, integrationUri: props.integrationUri, integrationMethod: props.method, + credentialsArn: props.credentials, + requestParameters: props.requestParameters, connectionId: props.connectionId, connectionType: props.connectionType, payloadFormatVersion: props.payloadFormatVersion?.version, + timeoutInMillis: props.timeout?.toMilliseconds(), }); if (props.secureServerName) { @@ -194,41 +241,78 @@ export interface IHttpRouteIntegration { */ export interface HttpRouteIntegrationConfig { /** - * Integration type. + * The description of the integration + * + * @default - undefined */ + readonly description?: string; + + /** + * Integration type. + */ readonly type: HttpIntegrationType; /** - * Integration URI - */ - readonly uri: string; + * Specifies the credentials ARN required for the integration, if any. + * + * @default - undefined + */ + readonly credentials?: string; /** - * The HTTP method that must be used to invoke the underlying proxy. - * Required for `HttpIntegrationType.HTTP_PROXY` - * @default - undefined - */ + * Specifies the AWS service action to invoke. + * + * @default - undefined + */ + readonly subtype?: string; + + /** + * Custom timeout for HTTP APIs + * + * @default - undefined + */ + readonly timeout?: Duration; + + /** + * Request parameters are a key-value map specifying parameters that are passed to AWS_PROXY integrations. + * + * @default - undefined + */ + readonly requestParameters?: { [key: string]: any }; + + /** + * Integration URI + * + * @default - undefined + */ + readonly uri?: string; + + /** + * The HTTP method that must be used to invoke the underlying proxy. + * Required for `HttpIntegrationType.HTTP_PROXY` + * @default - undefined + */ readonly method?: HttpMethod; /** - * The ID of the VPC link for a private integration. Supported only for HTTP APIs. - * - * @default - undefined - */ + * The ID of the VPC link for a private integration. Supported only for HTTP APIs. + * + * @default - undefined + */ readonly connectionId?: string; /** - * The type of the network connection to the integration endpoint - * - * @default HttpConnectionType.INTERNET - */ + * The type of the network connection to the integration endpoint + * + * @default HttpConnectionType.INTERNET + */ readonly connectionType?: HttpConnectionType; /** - * Payload format version in the case of lambda proxy integration - * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html - * @default - undefined - */ + * Payload format version in the case of lambda proxy integration + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html + * @default - undefined + */ readonly payloadFormatVersion: PayloadFormatVersion; /**