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(stepfunctions-tasks): Support calling ApiGateway REST and HTTP APIs #13033

Merged
merged 9 commits into from
Mar 10, 2021
Merged
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
44 changes: 44 additions & 0 deletions packages/@aws-cdk/aws-stepfunctions-tasks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aw
- [ResultPath](#resultpath)
- [Parameters](#task-parameters-from-the-state-json)
- [Evaluate Expression](#evaluate-expression)
- [API Gateway](#api-gateway)
- [Call REST API Endpoint](#call-rest-api-endpoint)
- [Call HTTP API Endpoint](#call-http-api-endpoint)
- [Athena](#athena)
- [StartQueryExecution](#startQueryExecution)
- [GetQueryExecution](#getQueryExecution)
Expand Down Expand Up @@ -217,6 +220,47 @@ The `EvaluateExpression` supports a `runtime` prop to specify the Lambda
runtime to use to evaluate the expression. Currently, only runtimes
of the Node.js family are supported.

## API Gateway

Step Functions supports [API Gateway](https://docs.aws.amazon.com/step-functions/latest/dg/connect-api-gateway.html) through the service integration pattern.

HTTP APIs are designed for low-latency, cost-effective integrations with AWS services, including AWS Lambda, and HTTP endpoints.
HTTP APIs support OIDC and OAuth 2.0 authorization, and come with built-in support for CORS and automatic deployments.
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).

### Call REST API Endpoint

The `CallApiGatewayRestApiEndpoint` calls the REST API endpoint.

```ts
import * as sfn from '@aws-cdk/aws-stepfunctions';
import * as tasks from `@aws-cdk/aws-stepfunctions-tasks`;

const restApi = new apigateway.RestApi(stack, 'MyRestApi');

const invokeTask = new tasks.CallApiGatewayRestApiEndpoint(stack, 'Call REST API', {
api: restApi,
stageName: 'prod',
method: HttpMethod.GET,
});
```

### Call HTTP API Endpoint

The `CallApiGatewayHttpApiEndpoint` calls the HTTP API endpoint.

```ts
import * as sfn from '@aws-cdk/aws-stepfunctions';
import * as tasks from `@aws-cdk/aws-stepfunctions-tasks`;

const httpApi = new apigatewayv2.HttpApi(stack, 'MyHttpApi');

const invokeTask = new tasks.CallApiGatewayHttpApiEndpoint(stack, 'Call HTTP API', {
api: httpApi,
method: HttpMethod.GET,
});
```

## Athena

Step Functions supports [Athena](https://docs.aws.amazon.com/step-functions/latest/dg/connect-athena.html) through the service integration pattern.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import * as sfn from '@aws-cdk/aws-stepfunctions';

/** Http Methods that API Gateway supports */
export enum HttpMethod {
/** Retreive data from a server at the specified resource */
GET = 'GET',

/** Send data to the API endpoint to create or udpate a resource */
POST = 'POST',

/** Send data to the API endpoint to update or create a resource */
PUT = 'PUT',

/** Delete the resource at the specified endpoint */
DELETE = 'DELETE',

/** Apply partial modifications to the resource */
PATCH = 'PATCH',

/** Retreive data from a server at the specified resource without the response body */
HEAD = 'HEAD',

/** Return data describing what other methods and operations the server supports */
OPTIONS = 'OPTIONS'
}

/**
* The authentication method used to call the endpoint
*/
export enum AuthType {
/** Call the API direclty with no authorization method */
NO_AUTH = 'NO_AUTH',

/** Use the IAM role associated with the current state machine for authorization */
IAM_ROLE = 'IAM_ROLE',

/** Use the resource policy of the API for authorization */
RESOURCE_POLICY = 'RESOURCE_POLICY',
}

/**
* Base CallApiGatewayEdnpoint Task Props
*/
export interface CallApiGatewayEndpointBaseProps extends sfn.TaskStateBaseProps {
/**
* Http method for the API
*/
readonly method: HttpMethod;

/**
* HTTP request information that does not relate to contents of the request
* @default - No headers
*/
readonly headers?: sfn.TaskInput;

ayush987goyal marked this conversation as resolved.
Show resolved Hide resolved
/**
* Path parameters appended after API endpoint
* @default - No path
*/
readonly apiPath?: string;

/**
* Query strings attatched to end of request
* @default - No query parameters
*/
readonly queryParameters?: sfn.TaskInput;

/**
* HTTP Request body
* @default - No request body
*/
readonly requestBody?: sfn.TaskInput;

/**
* Authentication methods
* @default AuthType.NO_AUTH
*/
readonly authType?: AuthType;
}
69 changes: 69 additions & 0 deletions packages/@aws-cdk/aws-stepfunctions-tasks/lib/apigateway/base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import * as iam from '@aws-cdk/aws-iam';
import * as sfn from '@aws-cdk/aws-stepfunctions';
import { Construct } from 'constructs';
import { integrationResourceArn, validatePatternSupported } from '../private/task-utils';
import { AuthType, CallApiGatewayEndpointBaseProps } from './base-types';

/**
* Base CallApiGatewayEndpoint Task
* @internal
*/
export abstract class CallApiGatewayEndpointBase extends sfn.TaskStateBase {
private static readonly SUPPORTED_INTEGRATION_PATTERNS: sfn.IntegrationPattern[] = [
sfn.IntegrationPattern.REQUEST_RESPONSE,
sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN,
];

private readonly baseProps: CallApiGatewayEndpointBaseProps;
private readonly integrationPattern: sfn.IntegrationPattern;

protected abstract readonly apiEndpoint: string;
protected abstract readonly arnForExecuteApi: string;
protected abstract readonly stageName?: string;

constructor(scope: Construct, id: string, props: CallApiGatewayEndpointBaseProps) {
super(scope, id, props);

this.baseProps = props;
this.integrationPattern = props.integrationPattern ?? sfn.IntegrationPattern.REQUEST_RESPONSE;
validatePatternSupported(this.integrationPattern, CallApiGatewayEndpointBase.SUPPORTED_INTEGRATION_PATTERNS);

if (this.integrationPattern === sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN) {
if (!sfn.FieldUtils.containsTaskToken(this.baseProps.headers)) {
throw new Error('Task Token is required in `headers` for WAIT_FOR_TASK_TOKEN pattern. Use JsonPath.taskToken to set the token.');
}
}
}

/**
* @internal
*/
protected _renderTask() {
return {
Resource: integrationResourceArn('apigateway', 'invoke', this.integrationPattern),
Parameters: sfn.FieldUtils.renderObject({
ApiEndpoint: this.apiEndpoint,
Method: this.baseProps.method,
Headers: this.baseProps.headers?.value,
Stage: this.stageName,
Path: this.baseProps.apiPath,
QueryParameters: this.baseProps.queryParameters?.value,
RequestBody: this.baseProps.requestBody?.value,
AuthType: this.baseProps.authType ? this.baseProps.authType : 'NO_AUTH',
}),
};
}

protected createPolicyStatements(): iam.PolicyStatement[] {
if (this.baseProps.authType === AuthType.NO_AUTH) {
return [];
}

return [
new iam.PolicyStatement({
resources: [this.arnForExecuteApi],
actions: ['execute-api:Invoke'],
}),
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import * as apigatewayv2 from '@aws-cdk/aws-apigatewayv2';
import * as iam from '@aws-cdk/aws-iam';
import * as sfn from '@aws-cdk/aws-stepfunctions';
import * as cdk from '@aws-cdk/core';
import { Construct } from 'constructs';
import { CallApiGatewayEndpointBase } from './base';
import { CallApiGatewayEndpointBaseProps } from './base-types';

/**
* Properties for calling an HTTP API Endpoint
*/
export interface CallApiGatewayHttpApiEndpointProps extends CallApiGatewayEndpointBaseProps {
/**
* API to call
*/
readonly api: apigatewayv2.IHttpApi;

/**
* Name of the stage where the API is deployed to in API Gateway
* @default '$default'
*/
readonly stageName?: string;
}

/**
* Call HTTP API endpoint as a Task
*
* @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-api-gateway.html
*/
export class CallApiGatewayHttpApiEndpoint extends CallApiGatewayEndpointBase {
protected readonly taskMetrics?: sfn.TaskMetricsConfig | undefined;
protected readonly taskPolicies?: iam.PolicyStatement[] | undefined;

protected readonly apiEndpoint: string;
protected readonly arnForExecuteApi: string;
protected readonly stageName?: string;

constructor(scope: Construct, id: string, private readonly props: CallApiGatewayHttpApiEndpointProps) {
super(scope, id, props);

this.apiEndpoint = this.getApiEndpoint();
this.arnForExecuteApi = this.getArnForExecuteApi();

this.taskPolicies = this.createPolicyStatements();
}

private getApiEndpoint(): string {
const apiStack = cdk.Stack.of(this.props.api);
return `${this.props.api.apiId}.execute-api.${apiStack.region}.${apiStack.urlSuffix}`;
}

private getArnForExecuteApi(): string {
const { api, stageName, method, apiPath } = this.props;

return cdk.Stack.of(api).formatArn({
service: 'execute-api',
resource: api.apiId,
sep: '/',
resourceName: `${stageName}/${method}${apiPath}`,
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import * as apigateway from '@aws-cdk/aws-apigateway';
import * as iam from '@aws-cdk/aws-iam';
import * as sfn from '@aws-cdk/aws-stepfunctions';
import * as cdk from '@aws-cdk/core';
import { Construct } from 'constructs';
import { CallApiGatewayEndpointBase } from './base';
import { CallApiGatewayEndpointBaseProps } from './base-types';

/**
* Properties for calling an REST API Endpoint
*/
export interface CallApiGatewayRestApiEndpointProps extends CallApiGatewayEndpointBaseProps {
/**
* API to call
*/
readonly api: apigateway.IRestApi;

/**
* Name of the stage where the API is deployed to in API Gateway
*/
readonly stageName: string;
}

/**
* Call REST API endpoint as a Task
*
* @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-api-gateway.html
*/
export class CallApiGatewayRestApiEndpoint extends CallApiGatewayEndpointBase {
protected readonly taskMetrics?: sfn.TaskMetricsConfig | undefined;
protected readonly taskPolicies?: iam.PolicyStatement[] | undefined;

protected readonly apiEndpoint: string;
protected readonly arnForExecuteApi: string;
protected readonly stageName?: string;

constructor(scope: Construct, id: string, private readonly props: CallApiGatewayRestApiEndpointProps) {
super(scope, id, props);

this.apiEndpoint = this.getApiEndpoint();
this.arnForExecuteApi = props.api.arnForExecuteApi(props.method, props.apiPath, props.stageName);
this.stageName = props.stageName;

this.taskPolicies = this.createPolicyStatements();
}

private getApiEndpoint(): string {
const apiStack = cdk.Stack.of(this.props.api);
return `${this.props.api.restApiId}.execute-api.${apiStack.region}.${apiStack.urlSuffix}`;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './base-types';
export * from './call-rest-api';
export * from './call-http-api';
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,4 @@ export * from './athena/get-query-execution';
export * from './athena/get-query-results';
export * from './databrew/start-job-run';
export * from './eks/call';
export * from './apigateway';
6 changes: 6 additions & 0 deletions packages/@aws-cdk/aws-stepfunctions-tasks/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@
"pkglint": "0.0.0"
},
"dependencies": {
"@aws-cdk/aws-apigateway": "0.0.0",
"@aws-cdk/aws-apigatewayv2": "0.0.0",
"@aws-cdk/aws-apigatewayv2-integrations": "0.0.0",
"@aws-cdk/aws-batch": "0.0.0",
"@aws-cdk/aws-cloudwatch": "0.0.0",
"@aws-cdk/aws-codebuild": "0.0.0",
Expand All @@ -95,6 +98,9 @@
},
"homepage": "https://github.com/aws/aws-cdk",
"peerDependencies": {
"@aws-cdk/aws-apigateway": "0.0.0",
"@aws-cdk/aws-apigatewayv2": "0.0.0",
"@aws-cdk/aws-apigatewayv2-integrations": "0.0.0",
"@aws-cdk/aws-batch": "0.0.0",
"@aws-cdk/aws-cloudwatch": "0.0.0",
"@aws-cdk/aws-codebuild": "0.0.0",
Expand Down
Loading