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. closes aws#15081.
- Loading branch information
Saqib Dhuka
committed
Oct 7, 2021
1 parent
6194ad4
commit f13f153
Showing
9 changed files
with
1,002 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
154 changes: 154 additions & 0 deletions
154
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,154 @@ | ||
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 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; | ||
|
||
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 = 'NewStateMachine-' + String(this.handler.stateMachineArn); | ||
} | ||
|
||
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') | ||
#else | ||
$input.path('$.output') | ||
#end`, | ||
}, | ||
}, | ||
...errorResponse, | ||
]; | ||
|
||
return integResponse; | ||
} | ||
|
||
function getRequestTemplates(handler: sfn.IStateMachine) { | ||
const templateString = getTemplateString(handler); | ||
const requestTemplate: { [contenType:string] : string } = | ||
{ | ||
'application/json': templateString, | ||
}; | ||
|
||
return requestTemplate; | ||
} | ||
|
||
function getTemplateString(handler: sfn.IStateMachine): string { | ||
const setStr: string = "#set($inputRoot = $input.path('$')) {"; | ||
const inputStr: string = '"input": "$util.escapeJavaScript($input.json(\'$\'))",'; | ||
const stateMachineStr: string = '"stateMachineArn":"'; | ||
const endStr: string = '"}'; | ||
const templateString: string = setStr + inputStr + stateMachineStr + String(handler.stateMachineArn) + endStr; | ||
|
||
return templateString; | ||
} |
124 changes: 124 additions & 0 deletions
124
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,124 @@ | ||
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; | ||
|
||
/** | ||
* Rest API props options | ||
* @default - no options. | ||
* | ||
*/ | ||
readonly options?: RestApiProps; | ||
} | ||
|
||
/** | ||
* Defines an API Gateway REST API with AWS Step Functions proxy integration. | ||
* | ||
*/ | ||
|
||
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(); | ||
|
||
let corsEnable; | ||
|
||
if (props.defaultCorsPreflightOptions !== undefined) { | ||
corsEnable = true; | ||
} else { | ||
corsEnable = false; | ||
} | ||
|
||
super(scope, id, { | ||
defaultIntegration: new StepFunctionsIntegration(props.handler, { | ||
credentialsRole: apiRole, | ||
proxy: false, //proxy not avaialble for Step Functions yet | ||
corsEnabled: corsEnable, | ||
}), | ||
...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, '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.