Skip to content

Commit

Permalink
issue aws#15081: StepFunctionsRestApi implemented. Checked through ma…
Browse files Browse the repository at this point in the history
…nual deployment. It is working as expected on the console.
  • Loading branch information
Saqib Dhuka committed Sep 21, 2021
1 parent 2a629dd commit a1af303
Show file tree
Hide file tree
Showing 4 changed files with 362 additions and 5 deletions.
153 changes: 153 additions & 0 deletions packages/@aws-cdk/aws-apigateway/lib/integrations/stepFunctions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import * as iam from '@aws-cdk/aws-iam';
import * as sfn from '@aws-cdk/aws-stepfunctions';
import { Token } from '@aws-cdk/core';
import { IntegrationConfig, IntegrationOptions, PassthroughBehavior } from '../integration';
import { Method } from '../method';
import { AwsIntegration } from './aws';

export interface StepFunctionsIntegrationOptions extends IntegrationOptions{
/**
* Use proxy integration or normal (request/response mapping) integration.
*
* @default false
*/
readonly proxy?: boolean;

/**
* Allow invoking method from AWS Console UI (for testing purposes).
*
* This will add another permission to the AWS Step Functions resource policy which
* will allow the `test-invoke-stage` stage to invoke this handler. If this
* is set to `false`, the function will only be usable from the deployment
* endpoint.
*
* @default true
*/
readonly allowTestInvoke?: boolean;

/**
* An IAM role that API Gateway assumes.
*
* @default - A role is not assumed
*/

readonly credentialsRole?: iam.IRole;


}
/**
* Integrates an AWS Step Functions to an API Gateway method.
*
* @example
*
* const handler = new sfn.StateMachine(this, 'MyStateMachine', ...);
* api.addMethod('GET', new StepFunctionsIntegration(handler));
*
*/

export class StepFunctionsIntegration extends AwsIntegration {
private readonly handler: sfn.IStateMachine;
// private readonly enableTest: boolean;

constructor(handler: sfn.IStateMachine, options: StepFunctionsIntegrationOptions = { }) {
//Proxy not yet available for State Machine
//When it available we can set proxy to be:
//proxy = options.proxy ?? true;
const proxy = false;

const integResponse = getIntegrationResponse();
const requestTemplates = getRequestTemplates(handler);

super({
proxy: proxy,
service: 'states',
action: 'StartSyncExecution',
options: {
credentialsRole: options.credentialsRole,
integrationResponses: integResponse,
passthroughBehavior: PassthroughBehavior.NEVER,
requestTemplates: requestTemplates,
},

});
this.handler = handler;
// this.enableTest = options.allowTestInvoke ?? true;
}

public bind(method: Method): IntegrationConfig {
const bindResult = super.bind(method);
const principal = new iam.ServicePrincipal('apigateway.amazonaws.com');

this.handler.grantExecution(principal, 'states:StartSyncExecution');


let stateMachineName;

if (this.handler instanceof sfn.StateMachine) {
//if not imported, extract the name from the CFN layer to reach the
//literal value if it is given (rather than a token)
stateMachineName = (this.handler.node.defaultChild as sfn.CfnStateMachine).stateMachineName;
} else {
throw Error('Not instance of StateMachine');
}

let deploymentToken;

if (!Token.isUnresolved(stateMachineName)) {
deploymentToken = JSON.stringify({ stateMachineName });
}
return {
...bindResult,
deploymentToken,
};

}
}

function getIntegrationResponse() {
const errorResponse = [
{
selectionPattern: '4\\d{2}',
statusCode: '400',
responseTemplates: {
'application/json': `{
"error": "Bad input!"
}`,
},
},
{
selectionPattern: '5\\d{2}',
statusCode: '500',
responseTemplates: {
'application/json': '"error": $input.path(\'$.error\')',
},
},
];

const integResponse = [
{
statusCode: '200',
responseTemplates: {
'application/json': '$input.path(\'$.output\')',
},
},
...errorResponse,
];

return integResponse;
}

function getRequestTemplates(handler: sfn.IStateMachine) {
const set_str: string = "#set($inputRoot = $input.path('$')) {";
const input_str: string = '"input": "$util.escapeJavaScript($input.json(\'$\'))",';
const stateMachine_str: string = '"stateMachineArn":"';
const end_str: string = '"}';
const templateString: string = set_str + input_str + stateMachine_str + String(handler.stateMachineArn) + end_str;

const requestTemplates: { [contenType:string] : string } =
{
'application/json': templateString,
};

return requestTemplates;
}
112 changes: 112 additions & 0 deletions packages/@aws-cdk/aws-apigateway/lib/stepFunctions-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { RestApi, RestApiProps } from '@aws-cdk/aws-apigateway';
import * as iam from '@aws-cdk/aws-iam';
import * as sfn from '@aws-cdk/aws-stepfunctions';
import { Construct } from 'constructs';
import { StepFunctionsIntegration } from './integrations/stepFunctions';
import { Model } from './model';

export interface StepFunctionsRestApiProps extends RestApiProps {
/**
* The default State Machine that handles all requests from this API.
*
* This handler will be used as a the default integration for all methods in
* this API, unless specified otherwise in `addMethod`.
*/
readonly handler: sfn.IStateMachine;

/**
* If true, route all requests to the State Machine
*
* If set to false, you will need to explicitly define the API model using
* `addResource` and `addMethod` (or `addProxy`).
*
* @default true
*/
readonly proxy?: boolean;

/**
* @default - no options.
*/
readonly options?: RestApiProps;
}

/**
* Defines an API Gateway REST API with AWS Step Functions proxy integration.
*
* Use the `proxy` property to define a greedy proxy ("{proxy+}") and "ANY"
* method from the specified path. If not defined, you will need to explicity
* add resources and methods to the API.
*/

export class StepFunctionsRestApi extends RestApi {
constructor(scope: Construct, id: string, props: StepFunctionsRestApiProps) {
if ((props.options && props.options.defaultIntegration) || props.defaultIntegration) {
throw new Error('Cannot specify "defaultIntegration" since Step Functions integration is automatically defined');
}

const apiRole = getRole(scope, props);
const methodResp = getMethodResponse();

super(scope, id, {
defaultIntegration: new StepFunctionsIntegration(props.handler, {
credentialsRole: apiRole,
}),
...props.options,
...props,
});

this.root.addMethod('ANY', new StepFunctionsIntegration(props.handler, {
credentialsRole: apiRole,
}), {
methodResponses: [
...methodResp,
],
});
}
}

function getRole(scope: Construct, props: StepFunctionsRestApiProps): iam.Role {
const apiName: string = props.handler + '-apiRole';
const apiRole = new iam.Role(scope, apiName, {
assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'),
});

apiRole.attachInlinePolicy(
new iam.Policy(scope, 'getPolicy', {
statements: [
new iam.PolicyStatement({
actions: ['states:StartSyncExecution'],
effect: iam.Effect.ALLOW,
resources: ['*'],
}),
],
}),
);

return apiRole;
}

function getMethodResponse() {
const methodResp = [
{
statusCode: '200',
responseModels: {
'application/json': Model.EMPTY_MODEL,
},
},
{
statusCode: '400',
responseModels: {
'application/json': Model.ERROR_MODEL,
},
},
{
statusCode: '500',
responseModels: {
'application/json': Model.ERROR_MODEL,
},
},
];

return methodResp;
}
4 changes: 4 additions & 0 deletions packages/@aws-cdk/aws-apigateway/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
"@aws-cdk/assert-internal": "0.0.0"
},
"dependencies": {
"@aws-cdk/aws-apigateway": "0.0.0",
"@aws-cdk/aws-certificatemanager": "0.0.0",
"@aws-cdk/aws-cloudwatch": "0.0.0",
"@aws-cdk/aws-cognito": "0.0.0",
Expand All @@ -91,12 +92,14 @@
"@aws-cdk/aws-logs": "0.0.0",
"@aws-cdk/aws-s3": "0.0.0",
"@aws-cdk/aws-s3-assets": "0.0.0",
"@aws-cdk/aws-stepfunctions": "0.0.0",
"@aws-cdk/core": "0.0.0",
"@aws-cdk/cx-api": "0.0.0",
"constructs": "^3.3.69"
},
"homepage": "https://github.com/aws/aws-cdk",
"peerDependencies": {
"@aws-cdk/aws-apigateway": "0.0.0",
"@aws-cdk/aws-certificatemanager": "0.0.0",
"@aws-cdk/aws-cloudwatch": "0.0.0",
"@aws-cdk/aws-cognito": "0.0.0",
Expand All @@ -107,6 +110,7 @@
"@aws-cdk/aws-logs": "0.0.0",
"@aws-cdk/aws-s3": "0.0.0",
"@aws-cdk/aws-s3-assets": "0.0.0",
"@aws-cdk/aws-stepfunctions": "0.0.0",
"@aws-cdk/core": "0.0.0",
"@aws-cdk/cx-api": "0.0.0",
"constructs": "^3.3.69"
Expand Down
Loading

0 comments on commit a1af303

Please sign in to comment.