Aws-cdk: API Gateway: AwsIntegration with Step Functions

Created on 2 Jan 2019  路  6Comments  路  Source: aws/aws-cdk

Came up as a question on Gitter:

Hi I am new to CDK and trying to "Create a Step Functions API Using API Gateway" using java as follows

RestApi api = new RestApi(this, "api");
        AwsIntegrationProps awsIntegrationProps = AwsIntegrationProps.builder().withService("Step Functions")
            .withAction("StartExecution").build();

    AwsIntegration awsIntegration = new AwsIntegration(awsIntegrationProps);
    Resource resource = api.getRoot().addResource("execution");
    resource.addMethod("POST", awsIntegration);

But i am getting Error "Role ARN must be specified for AWS integrations (Service: AmazonApiGateway; Status Code: 400; Error Code: BadRequestException; Request ID: fa2f7fc8-0d5c-11e9-b2bf-ab94b16eea0c)"
How do i specify Execution Role ? Ref aws doc https://docs.aws.amazon.com/step-functions/latest/dg/tutorial-api-gateway.html

AWS integrations apparently always need a Role. The correct solution is to pass a RoleARN under the options key, but this is is validation plus a hint that we could have done locally.

Even better, we should make it as easy as on other L2 constructs to construct this required role (by doing so implicitly). We should also look into a way to set up the right permissions automatically.

@aws-cdaws-apigateway efformedium feature-request p2

Most helpful comment

@jindriago-scf Thank you for helping!
I could understand your code and achieved implimenting in TypeScript.

All 6 comments

Could we have an example of how this can be achieved?

Via the aws_apigateway.IntegrationOptions(credentials_role)?
or the root.add_method(options)?

I'm struggled with the same problem in typescript.
Could someone give me a hint to solve this?

@fumiyakk We did it this way, its in python but I think can be translated to any language.

class AwsStateMachineIntegration(apig.AwsIntegration):
    def __init__(
            self,
            state_machine: StateMachine,
            request_templates: typing.Optional[typing.Dict[str, str]] = None,
            integration_responses: typing.Optional[typing.List[apig.IntegrationResponse]] = None,
            action_parameters: typing.Optional[typing.Mapping[str, str]] = None,
            integration_http_method: typing.Optional[str] = None,
            path: typing.Optional[str] = None,
            proxy: typing.Optional[bool] = None,
            subdomain: typing.Optional[str] = None):

        if not integration_responses:
            integration_responses = [
                apig.IntegrationResponse(
                    selection_pattern="200",
                    status_code="200",
                    response_templates={
                        "application/json": """{
                                    "executionToken": "$input.json('$.executionArn').split(':')[7].replace('"', "")"
                                }"""
                    }
                )
            ]

        # This is a velocity template that takes all the params in the url and inject them in the body.
        # it also takes the path and inject it in the body as an attribute path
        if not request_templates:
            request_templates = {
                "application/json": """#set($allParams = $input.params())
                        #set($pathParams = $allParams.get('path'))
                        #set($inputStr = "")
                        #set($jsonString = $input.body.substring(1))
                        #foreach($pathParamName in $pathParams.keySet())
                            #set($inputStr = $inputStr +'"'+ $pathParamName + '": "' + $util.escapeJavaScript($pathParams.get($pathParamName)) + '"')
                            #if($foreach.hasNext)  #set($inputStr = $inputStr + ',')#end
                        #end
                        #if($pathParams.size()>0) #set($inputStr = $inputStr + ",") #end
                        {"input": "{$util.escapeJavaScript($inputStr) $util.escapeJavaScript($jsonString)",
                         "stateMachineArn": \"""" + state_machine.state_machine_arn
                                    + """"
                        }"""
            }
        start_execution_role = self._get_start_execution_role(state_machine)

        super().__init__(
            service="states",
            action="StartExecution",
            action_parameters=action_parameters,
            integration_http_method=integration_http_method,
            options=IntegrationOptions(
                credentials_role=start_execution_role,
                integration_responses=integration_responses,
                request_templates=request_templates,
                passthrough_behavior=apig.PassthroughBehavior.NEVER
            ),
            path=path,
            proxy=proxy,
            subdomain=subdomain)

    @staticmethod
    def _get_start_execution_role(
            state_machine: StateMachine) -> iam.Role:
        start_execution_policy = iam.Policy(
            scope=state_machine,
            id=f"Start{state_machine.node.id}Policy",
            statements=[
                iam.PolicyStatement(
                    actions=[step_functions.Action.START_EXECUTION.value],
                    resources=[state_machine.state_machine_arn])])
        start_execution_role = iam.Role(
            scope=state_machine,
            id=f"{state_machine.node.id}ExecutionRole",
            assumed_by=iam.ServicePrincipal(
                service="apigateway.amazonaws.com"))
        start_execution_role.attach_inline_policy(
            start_execution_policy)
        return start_execution_role

I don't have too much time to explain it all at once, but hope it helps. Either way if you don't understand something let me know!

@jindriago-scf Thank you for helping!
I could understand your code and achieved implimenting in TypeScript.

@fumiyakk Any chance you could share your code? :)

@Awlsring
Sorry. I couldn't show the raw code because it's for work.
So, I made some abstract code. It might be broken.
I hope this could help you.

import * as cdk from '@aws-cdk/core';
import * as apigw from '@aws-cdk/aws-apigateway';
import * as iam from '@aws-cdk/aws-iam';

type Arns = { [key: string]: string }

export class SampleApiGatewayStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, Arns: Arns, props?: cdk.StackProps) {
    super(scope, id, props);

    const api = new apigw.RestApi(this, 'Api', {
      restApiName: 'stateApiName',
    });

    const sampleRole = new iam.Role(this, 'StepFunctionRole',{
      assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com')
    });

    sampleRole.addToPolicy(new iam.PolicyStatement({
      resources: ['*'],
      actions: ["states:*"]
    }));

    // Get StepFunctionArn
    const str1: string = "#set($inputRoot = $input.path('$')) {"
    const str2: string = `"input": "$util.escapeJavaScript($input.json('$'))",`
    const str3: string = `"stateMachineArn":"` 
    const str4: string = `"}`
    const templateString: string = str1 + str2 + str3 + Arns["stateMachineArn"] + str4

    const requestTemplates = {
      "application/json": templateString
    }

    // Connect stateMachine with api gateway
    const stateMachineIntegration = new apigw.AwsIntegration({
      service: "states",
      action: "sampleAction",
      options: {
        credentialsRole: sampleRole,
        requestTemplates: requestTemplates, 
      }
    });

    const process1 = api.root.addResource('preprocessing')
    const process2 = process1.addResource('v1')

    process2.addMethod('POST', stateMachineIntegration, {
      methodResponses: [{
        statusCode: '200',
      }]
    });
 }
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

pepastach picture pepastach  路  3Comments

v-do picture v-do  路  3Comments

nzspambot picture nzspambot  路  3Comments

abelmokadem picture abelmokadem  路  3Comments

slipdexic picture slipdexic  路  3Comments