Skip to content

Commit

Permalink
feat(stepfunctions-tasks): Support calling ApiGateway REST and HTTP A…
Browse files Browse the repository at this point in the history
…PIs (#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*
  • Loading branch information
ayush987goyal authored Mar 10, 2021
1 parent 78b265c commit cc608d0
Show file tree
Hide file tree
Showing 14 changed files with 1,359 additions and 0 deletions.
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;

/**
* 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

0 comments on commit cc608d0

Please sign in to comment.