forked from aws/aws-cdk
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
StepFunctionsRestApi implemented along with unit and integration test…
…ing. Fixed Integration test and generated expected json for stepFunctionsRestApi Stack deployment. Added code snippet to the README. Removing restApiprops option as the composition in StepFunctionsRestApiProps. closes aws#15081.
- Loading branch information
Saqib Dhuka
committed
Oct 18, 2021
1 parent
00a8063
commit 933399b
Showing
10 changed files
with
1,070 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
158 changes: 158 additions & 0 deletions
158
packages/@aws-cdk/aws-apigateway/lib/integrations/stepFunctions.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
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'; | ||
|
||
/** | ||
* StepFunctionsIntegrationOptions | ||
*/ | ||
export interface StepFunctionsIntegrationOptions extends IntegrationOptions { | ||
/** | ||
* Use proxy integration or normal (request/response mapping) integration. | ||
* | ||
* @default false | ||
*/ | ||
readonly proxy?: boolean; | ||
|
||
/** | ||
* Check if cors is enabled | ||
* @default false | ||
*/ | ||
readonly corsEnabled?: boolean; | ||
|
||
} | ||
/** | ||
* Integrates a Synchronous Express State Machine from 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; | ||
|
||
constructor(handler: sfn.IStateMachine, options: StepFunctionsIntegrationOptions = { }) { | ||
|
||
const integResponse = getIntegrationResponse(); | ||
const requestTemplate = getRequestTemplates(handler); | ||
|
||
if (options.corsEnabled) { | ||
super({ | ||
proxy: options.proxy, | ||
service: 'states', | ||
action: 'StartSyncExecution', | ||
options, | ||
}); | ||
} else { | ||
super({ | ||
proxy: options.proxy, | ||
service: 'states', | ||
action: 'StartSyncExecution', | ||
options: { | ||
credentialsRole: options.credentialsRole, | ||
integrationResponses: integResponse, | ||
passthroughBehavior: PassthroughBehavior.NEVER, | ||
requestTemplates: requestTemplate, | ||
}, | ||
}); | ||
} | ||
|
||
this.handler = handler; | ||
} | ||
|
||
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 { | ||
stateMachineName = 'StateMachine-' + (String(this.handler.stack.node.addr).substring(0, 8)); | ||
} | ||
|
||
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': `#set($inputRoot = $input.path('$')) | ||
#if($input.path('$.status').toString().equals("FAILED")) | ||
#set($context.responseOverride.status = 500) | ||
{ | ||
"error": "$input.path('$.error')", | ||
"cause": "$input.path('$.cause')" | ||
} | ||
#else | ||
$input.path('$.output') | ||
#end`, | ||
}, | ||
}, | ||
...errorResponse, | ||
]; | ||
|
||
return integResponse; | ||
} | ||
|
||
function getRequestTemplates(handler: sfn.IStateMachine) { | ||
const templateString = getTemplateString(handler); | ||
|
||
const requestTemplate: { [contentType:string] : string } = | ||
{ | ||
'application/json': templateString, | ||
}; | ||
|
||
return requestTemplate; | ||
} | ||
|
||
function getTemplateString(handler: sfn.IStateMachine): string { | ||
const templateString: string = ` | ||
#set($inputRoot = $input.path('$')) { | ||
"input": "$util.escapeJavaScript($input.json(\'$\'))", | ||
"stateMachineArn": "${handler.stateMachineArn}" | ||
}`; | ||
|
||
return templateString; | ||
} |
116 changes: 116 additions & 0 deletions
116
packages/@aws-cdk/aws-apigateway/lib/stepFunctions-api.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
import * as iam from '@aws-cdk/aws-iam'; | ||
import * as sfn from '@aws-cdk/aws-stepfunctions'; | ||
import { Construct } from 'constructs'; | ||
import { RestApi, RestApiProps } from '.'; | ||
import { StepFunctionsIntegration } from './integrations/stepFunctions'; | ||
import { Model } from './model'; | ||
|
||
/** | ||
* StepFunctionsRestApiProps | ||
*/ | ||
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; | ||
} | ||
|
||
/** | ||
* Defines an API Gateway REST API with a Synchrounous Express State Machine as a proxy integration. | ||
* | ||
*/ | ||
|
||
export class StepFunctionsRestApi extends RestApi { | ||
constructor(scope: Construct, id: string, props: StepFunctionsRestApiProps) { | ||
if (props.defaultIntegration) { | ||
throw new Error('Cannot specify "defaultIntegration" since Step Functions integration is automatically defined'); | ||
} | ||
|
||
const apiRole = getRole(scope, props); | ||
const methodResp = getMethodResponse(); | ||
|
||
let corsEnabled; | ||
|
||
if (props.defaultCorsPreflightOptions !== undefined) { | ||
corsEnabled = true; | ||
} else { | ||
corsEnabled = false; | ||
} | ||
|
||
super(scope, id, { | ||
defaultIntegration: new StepFunctionsIntegration(props.handler, { | ||
credentialsRole: apiRole, | ||
proxy: false, //proxy not avaialble for Step Functions yet | ||
corsEnabled: corsEnabled, | ||
}), | ||
...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, 'AllowStartSyncExecution', { | ||
statements: [ | ||
new iam.PolicyStatement({ | ||
actions: ['states:StartSyncExecution'], | ||
effect: iam.Effect.ALLOW, | ||
resources: [props.handler.stateMachineArn], | ||
}), | ||
], | ||
}), | ||
); | ||
|
||
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.