Skip to content

Commit cc608d0

Browse files
feat(stepfunctions-tasks): Support calling ApiGateway REST and HTTP APIs (#13033)
feat(stepfunctions-tasks): Support calling APIGW REST and HTTP APIs Taking ownership of the original PR #11565 by @Sumeet-Badyal API as per documentation here: https://docs.aws.amazon.com/step-functions/latest/dg/connect-api-gateway.html https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-control-access-using-iam-policies-to-invoke-api.html closes #11566 closes #11565 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 78b265c commit cc608d0

14 files changed

+1359
-0
lines changed

packages/@aws-cdk/aws-stepfunctions-tasks/README.md

+44
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aw
2828
- [ResultPath](#resultpath)
2929
- [Parameters](#task-parameters-from-the-state-json)
3030
- [Evaluate Expression](#evaluate-expression)
31+
- [API Gateway](#api-gateway)
32+
- [Call REST API Endpoint](#call-rest-api-endpoint)
33+
- [Call HTTP API Endpoint](#call-http-api-endpoint)
3134
- [Athena](#athena)
3235
- [StartQueryExecution](#startQueryExecution)
3336
- [GetQueryExecution](#getQueryExecution)
@@ -217,6 +220,47 @@ The `EvaluateExpression` supports a `runtime` prop to specify the Lambda
217220
runtime to use to evaluate the expression. Currently, only runtimes
218221
of the Node.js family are supported.
219222

223+
## API Gateway
224+
225+
Step Functions supports [API Gateway](https://docs.aws.amazon.com/step-functions/latest/dg/connect-api-gateway.html) through the service integration pattern.
226+
227+
HTTP APIs are designed for low-latency, cost-effective integrations with AWS services, including AWS Lambda, and HTTP endpoints.
228+
HTTP APIs support OIDC and OAuth 2.0 authorization, and come with built-in support for CORS and automatic deployments.
229+
Previous-generation REST APIs currently offer more features. More details can be found [here](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vs-rest.html).
230+
231+
### Call REST API Endpoint
232+
233+
The `CallApiGatewayRestApiEndpoint` calls the REST API endpoint.
234+
235+
```ts
236+
import * as sfn from '@aws-cdk/aws-stepfunctions';
237+
import * as tasks from `@aws-cdk/aws-stepfunctions-tasks`;
238+
239+
const restApi = new apigateway.RestApi(stack, 'MyRestApi');
240+
241+
const invokeTask = new tasks.CallApiGatewayRestApiEndpoint(stack, 'Call REST API', {
242+
api: restApi,
243+
stageName: 'prod',
244+
method: HttpMethod.GET,
245+
});
246+
```
247+
248+
### Call HTTP API Endpoint
249+
250+
The `CallApiGatewayHttpApiEndpoint` calls the HTTP API endpoint.
251+
252+
```ts
253+
import * as sfn from '@aws-cdk/aws-stepfunctions';
254+
import * as tasks from `@aws-cdk/aws-stepfunctions-tasks`;
255+
256+
const httpApi = new apigatewayv2.HttpApi(stack, 'MyHttpApi');
257+
258+
const invokeTask = new tasks.CallApiGatewayHttpApiEndpoint(stack, 'Call HTTP API', {
259+
api: httpApi,
260+
method: HttpMethod.GET,
261+
});
262+
```
263+
220264
## Athena
221265

222266
Step Functions supports [Athena](https://docs.aws.amazon.com/step-functions/latest/dg/connect-athena.html) through the service integration pattern.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import * as sfn from '@aws-cdk/aws-stepfunctions';
2+
3+
/** Http Methods that API Gateway supports */
4+
export enum HttpMethod {
5+
/** Retreive data from a server at the specified resource */
6+
GET = 'GET',
7+
8+
/** Send data to the API endpoint to create or udpate a resource */
9+
POST = 'POST',
10+
11+
/** Send data to the API endpoint to update or create a resource */
12+
PUT = 'PUT',
13+
14+
/** Delete the resource at the specified endpoint */
15+
DELETE = 'DELETE',
16+
17+
/** Apply partial modifications to the resource */
18+
PATCH = 'PATCH',
19+
20+
/** Retreive data from a server at the specified resource without the response body */
21+
HEAD = 'HEAD',
22+
23+
/** Return data describing what other methods and operations the server supports */
24+
OPTIONS = 'OPTIONS'
25+
}
26+
27+
/**
28+
* The authentication method used to call the endpoint
29+
*/
30+
export enum AuthType {
31+
/** Call the API direclty with no authorization method */
32+
NO_AUTH = 'NO_AUTH',
33+
34+
/** Use the IAM role associated with the current state machine for authorization */
35+
IAM_ROLE = 'IAM_ROLE',
36+
37+
/** Use the resource policy of the API for authorization */
38+
RESOURCE_POLICY = 'RESOURCE_POLICY',
39+
}
40+
41+
/**
42+
* Base CallApiGatewayEdnpoint Task Props
43+
*/
44+
export interface CallApiGatewayEndpointBaseProps extends sfn.TaskStateBaseProps {
45+
/**
46+
* Http method for the API
47+
*/
48+
readonly method: HttpMethod;
49+
50+
/**
51+
* HTTP request information that does not relate to contents of the request
52+
* @default - No headers
53+
*/
54+
readonly headers?: sfn.TaskInput;
55+
56+
/**
57+
* Path parameters appended after API endpoint
58+
* @default - No path
59+
*/
60+
readonly apiPath?: string;
61+
62+
/**
63+
* Query strings attatched to end of request
64+
* @default - No query parameters
65+
*/
66+
readonly queryParameters?: sfn.TaskInput;
67+
68+
/**
69+
* HTTP Request body
70+
* @default - No request body
71+
*/
72+
readonly requestBody?: sfn.TaskInput;
73+
74+
/**
75+
* Authentication methods
76+
* @default AuthType.NO_AUTH
77+
*/
78+
readonly authType?: AuthType;
79+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import * as iam from '@aws-cdk/aws-iam';
2+
import * as sfn from '@aws-cdk/aws-stepfunctions';
3+
import { Construct } from 'constructs';
4+
import { integrationResourceArn, validatePatternSupported } from '../private/task-utils';
5+
import { AuthType, CallApiGatewayEndpointBaseProps } from './base-types';
6+
7+
/**
8+
* Base CallApiGatewayEndpoint Task
9+
* @internal
10+
*/
11+
export abstract class CallApiGatewayEndpointBase extends sfn.TaskStateBase {
12+
private static readonly SUPPORTED_INTEGRATION_PATTERNS: sfn.IntegrationPattern[] = [
13+
sfn.IntegrationPattern.REQUEST_RESPONSE,
14+
sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN,
15+
];
16+
17+
private readonly baseProps: CallApiGatewayEndpointBaseProps;
18+
private readonly integrationPattern: sfn.IntegrationPattern;
19+
20+
protected abstract readonly apiEndpoint: string;
21+
protected abstract readonly arnForExecuteApi: string;
22+
protected abstract readonly stageName?: string;
23+
24+
constructor(scope: Construct, id: string, props: CallApiGatewayEndpointBaseProps) {
25+
super(scope, id, props);
26+
27+
this.baseProps = props;
28+
this.integrationPattern = props.integrationPattern ?? sfn.IntegrationPattern.REQUEST_RESPONSE;
29+
validatePatternSupported(this.integrationPattern, CallApiGatewayEndpointBase.SUPPORTED_INTEGRATION_PATTERNS);
30+
31+
if (this.integrationPattern === sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN) {
32+
if (!sfn.FieldUtils.containsTaskToken(this.baseProps.headers)) {
33+
throw new Error('Task Token is required in `headers` for WAIT_FOR_TASK_TOKEN pattern. Use JsonPath.taskToken to set the token.');
34+
}
35+
}
36+
}
37+
38+
/**
39+
* @internal
40+
*/
41+
protected _renderTask() {
42+
return {
43+
Resource: integrationResourceArn('apigateway', 'invoke', this.integrationPattern),
44+
Parameters: sfn.FieldUtils.renderObject({
45+
ApiEndpoint: this.apiEndpoint,
46+
Method: this.baseProps.method,
47+
Headers: this.baseProps.headers?.value,
48+
Stage: this.stageName,
49+
Path: this.baseProps.apiPath,
50+
QueryParameters: this.baseProps.queryParameters?.value,
51+
RequestBody: this.baseProps.requestBody?.value,
52+
AuthType: this.baseProps.authType ? this.baseProps.authType : 'NO_AUTH',
53+
}),
54+
};
55+
}
56+
57+
protected createPolicyStatements(): iam.PolicyStatement[] {
58+
if (this.baseProps.authType === AuthType.NO_AUTH) {
59+
return [];
60+
}
61+
62+
return [
63+
new iam.PolicyStatement({
64+
resources: [this.arnForExecuteApi],
65+
actions: ['execute-api:Invoke'],
66+
}),
67+
];
68+
}
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import * as apigatewayv2 from '@aws-cdk/aws-apigatewayv2';
2+
import * as iam from '@aws-cdk/aws-iam';
3+
import * as sfn from '@aws-cdk/aws-stepfunctions';
4+
import * as cdk from '@aws-cdk/core';
5+
import { Construct } from 'constructs';
6+
import { CallApiGatewayEndpointBase } from './base';
7+
import { CallApiGatewayEndpointBaseProps } from './base-types';
8+
9+
/**
10+
* Properties for calling an HTTP API Endpoint
11+
*/
12+
export interface CallApiGatewayHttpApiEndpointProps extends CallApiGatewayEndpointBaseProps {
13+
/**
14+
* API to call
15+
*/
16+
readonly api: apigatewayv2.IHttpApi;
17+
18+
/**
19+
* Name of the stage where the API is deployed to in API Gateway
20+
* @default '$default'
21+
*/
22+
readonly stageName?: string;
23+
}
24+
25+
/**
26+
* Call HTTP API endpoint as a Task
27+
*
28+
* @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-api-gateway.html
29+
*/
30+
export class CallApiGatewayHttpApiEndpoint extends CallApiGatewayEndpointBase {
31+
protected readonly taskMetrics?: sfn.TaskMetricsConfig | undefined;
32+
protected readonly taskPolicies?: iam.PolicyStatement[] | undefined;
33+
34+
protected readonly apiEndpoint: string;
35+
protected readonly arnForExecuteApi: string;
36+
protected readonly stageName?: string;
37+
38+
constructor(scope: Construct, id: string, private readonly props: CallApiGatewayHttpApiEndpointProps) {
39+
super(scope, id, props);
40+
41+
this.apiEndpoint = this.getApiEndpoint();
42+
this.arnForExecuteApi = this.getArnForExecuteApi();
43+
44+
this.taskPolicies = this.createPolicyStatements();
45+
}
46+
47+
private getApiEndpoint(): string {
48+
const apiStack = cdk.Stack.of(this.props.api);
49+
return `${this.props.api.apiId}.execute-api.${apiStack.region}.${apiStack.urlSuffix}`;
50+
}
51+
52+
private getArnForExecuteApi(): string {
53+
const { api, stageName, method, apiPath } = this.props;
54+
55+
return cdk.Stack.of(api).formatArn({
56+
service: 'execute-api',
57+
resource: api.apiId,
58+
sep: '/',
59+
resourceName: `${stageName}/${method}${apiPath}`,
60+
});
61+
}
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import * as apigateway from '@aws-cdk/aws-apigateway';
2+
import * as iam from '@aws-cdk/aws-iam';
3+
import * as sfn from '@aws-cdk/aws-stepfunctions';
4+
import * as cdk from '@aws-cdk/core';
5+
import { Construct } from 'constructs';
6+
import { CallApiGatewayEndpointBase } from './base';
7+
import { CallApiGatewayEndpointBaseProps } from './base-types';
8+
9+
/**
10+
* Properties for calling an REST API Endpoint
11+
*/
12+
export interface CallApiGatewayRestApiEndpointProps extends CallApiGatewayEndpointBaseProps {
13+
/**
14+
* API to call
15+
*/
16+
readonly api: apigateway.IRestApi;
17+
18+
/**
19+
* Name of the stage where the API is deployed to in API Gateway
20+
*/
21+
readonly stageName: string;
22+
}
23+
24+
/**
25+
* Call REST API endpoint as a Task
26+
*
27+
* @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-api-gateway.html
28+
*/
29+
export class CallApiGatewayRestApiEndpoint extends CallApiGatewayEndpointBase {
30+
protected readonly taskMetrics?: sfn.TaskMetricsConfig | undefined;
31+
protected readonly taskPolicies?: iam.PolicyStatement[] | undefined;
32+
33+
protected readonly apiEndpoint: string;
34+
protected readonly arnForExecuteApi: string;
35+
protected readonly stageName?: string;
36+
37+
constructor(scope: Construct, id: string, private readonly props: CallApiGatewayRestApiEndpointProps) {
38+
super(scope, id, props);
39+
40+
this.apiEndpoint = this.getApiEndpoint();
41+
this.arnForExecuteApi = props.api.arnForExecuteApi(props.method, props.apiPath, props.stageName);
42+
this.stageName = props.stageName;
43+
44+
this.taskPolicies = this.createPolicyStatements();
45+
}
46+
47+
private getApiEndpoint(): string {
48+
const apiStack = cdk.Stack.of(this.props.api);
49+
return `${this.props.api.restApiId}.execute-api.${apiStack.region}.${apiStack.urlSuffix}`;
50+
}
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './base-types';
2+
export * from './call-rest-api';
3+
export * from './call-http-api';

packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,4 @@ export * from './athena/get-query-execution';
4545
export * from './athena/get-query-results';
4646
export * from './databrew/start-job-run';
4747
export * from './eks/call';
48+
export * from './apigateway';

packages/@aws-cdk/aws-stepfunctions-tasks/package.json

+6
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@
7272
"pkglint": "0.0.0"
7373
},
7474
"dependencies": {
75+
"@aws-cdk/aws-apigateway": "0.0.0",
76+
"@aws-cdk/aws-apigatewayv2": "0.0.0",
77+
"@aws-cdk/aws-apigatewayv2-integrations": "0.0.0",
7578
"@aws-cdk/aws-batch": "0.0.0",
7679
"@aws-cdk/aws-cloudwatch": "0.0.0",
7780
"@aws-cdk/aws-codebuild": "0.0.0",
@@ -95,6 +98,9 @@
9598
},
9699
"homepage": "https://github.com/aws/aws-cdk",
97100
"peerDependencies": {
101+
"@aws-cdk/aws-apigateway": "0.0.0",
102+
"@aws-cdk/aws-apigatewayv2": "0.0.0",
103+
"@aws-cdk/aws-apigatewayv2-integrations": "0.0.0",
98104
"@aws-cdk/aws-batch": "0.0.0",
99105
"@aws-cdk/aws-cloudwatch": "0.0.0",
100106
"@aws-cdk/aws-codebuild": "0.0.0",

0 commit comments

Comments
 (0)