Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(apigatewayv2): add aws service integration -- step functions #14498

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions packages/@aws-cdk/aws-apigatewayv2-integrations/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
- [Request Parameters](#request-parameters)
- [WebSocket APIs](#websocket-apis)
Expand Down Expand Up @@ -79,6 +80,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
Expand Down
178 changes: 178 additions & 0 deletions packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/aws.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import { ParameterMapping } from '@aws-cdk/aws-apigatewayv2';
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;

/**
* Specifies how to transform HTTP requests before sending them to the backend
* @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-parameter-mapping.html
* @default undefined requests are sent to the backend unmodified
*/
readonly parameterMapping?: ParameterMapping;

}

/**
* The StepFunctions-StartExecution integration resource for HTTP API
*/
export class StepFunctionsStartExecutionIntegration extends StepFunctionsIntegration {
Comment on lines +68 to +70
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this one says StartExecution but later on L79 you are giving permission to StartSyncExecution.

Also resources [*] should be avoided if you have the state machine.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good Note. I will fix this. Thank you


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,
ParameterMapping: this._props.parameterMapping?.mappings,
};
}
}

/**
* 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');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you please clarify what is this line of code doing?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It grants the execution permission, but does not attach the policy. That needs to be done explicitly.

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,
ParameterMapping: this._props.parameterMapping?.mappings,
};
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { HttpMethod, IVpcLink, ParameterMapping } from '@aws-cdk/aws-apigatewayv2';
import { Duration } from '@aws-cdk/core';

/**
* Base options for private integration
Expand Down Expand Up @@ -32,3 +33,23 @@ export interface HttpPrivateIntegrationOptions {
*/
readonly parameterMapping?: ParameterMapping;
}

/**
* 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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './nlb';
export * from './service-discovery';
export * from './http-proxy';
export * from './lambda';
export * from './aws';
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ import {
HttpRouteIntegrationConfig,
IHttpRouteIntegration,
PayloadFormatVersion,
ParameterMapping,
HttpMethod,
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';


/**
Expand Down Expand Up @@ -63,3 +66,71 @@ 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);

const requestParam = this._buildRequestParameters();
const mapping = new ParameterMapping();
mapping.custom('StateMachineArn', String(requestParam.StateMachineArn));
mapping.custom('Name', String(requestParam.Name));
mapping.custom('Input', String(requestParam.Input));

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,
parameterMapping: mapping,
};
}

}
7 changes: 6 additions & 1 deletion packages/@aws-cdk/aws-apigatewayv2-integrations/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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"
},
Expand All @@ -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"
},
Expand Down
Loading