Passing ARN reference from CloudFormation to Swagger

7.5k views Asked by At

We are trying to automate the deployment of AWS lambda and API gateway using Amazon CloudFormation and Swagger. Towards this, we have created a CloudFormation template to create the Lambda and other resources required for APIGateway (including the endpoints). We would like to import the API definitions from an external swagger file so that the same CloudFormation template can be used for multiple lambdas and APIGateways. Is there a way we can refer the ARN of the lambda which has been created by the CloudFormation template in the external swagger file (being referred to in the same CloudFormation template) which holds the API definition?

Swagger content:

"x-amazon-apigateway-integration": {
              "uri": "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:123456789012:function:TestSimpleProxy/invocations",
              "passthroughBehavior": "when_no_match",
              "httpMethod": "POST",
              "type": "aws_proxy"
            }

In the above integration method I need to replace the value of the uri dynamically from the cloud formation template.

My cloud formation script is as below:

"myApi":{
      "Type" : "AWS::ApiGateway::RestApi",
      "Properties" : {
        "BodyS3Location" : S3Location of the swagger definition file,
        ..,
        ..
      }
    }
4

There are 4 answers

2
Capt. Crunch On

Is that "x-amazon-apigateway-integration" part of your CloudFormation template?

If so, then following the example from https://blog.jayway.com/2016/09/18/introduction-swagger-cloudformation-api-gateway/, I think you can use Ref function to pass that information through.

0
jandersen On

I just ran into the same obstacle... This is NOT a simple answer but a workaround to automate the resolution of the Lambda ARN that you want to use in the swagger template; it's working for me. Here goes...

  1. Export the Lambda ARN in the CF template that creates your Lambda and which you want to later use in the swagger template e.g.

    Outputs:
      MyLambdaARN:
        Value: !Ref "LambdaFuncThatIsDefinedInTheTemplate"
        Export:
          Name: !Sub "${AWS::StackName}-LambdaARN"
    
  2. Refer to the Lambda ARN Export within the swagger template - In place of the ARN for your lambda, enter a replaceable token that includes the export name from step 1, for example "MyLambdaStack-LambdaARN" if your stack from step 1 was created as "MyLambdaStack".

    • For the token syntax let's use what we wish actually worked, the ImportValue function so our token becomes !ImportValue(MyLambdaStack-LambdaARN).
    • Within our swagger template, the integration extension uses our token like this:

      x-amazon-apigateway-integration:
        passthroughBehavior: "when_no_match"
        uri: "!ImportValue(MyLambdaStack-LambdaARN)"
        httpMethod": "POST"
        type: "AWS-PROXY" 
      
  3. Replace Token with Actual Lambda ARN - We'll use the following script to replace the !ImportValue(MyLambdaStack-LambdaARN) token with the actual Lambda ARN we need there.

    #!/bin/bash
    SWAGGER_FILE=$1
    
    REPLACEMENTS=$( \
        aws cloudformation list-exports --query 'Exports[*].[Name,Value]'\
        | awk '{print "s/!ImportValue(" $1 ")/" $2 "/g"}' ORS='; '\
    )
    
    sed "$REPLACEMENTS" "$SWAGGER_FILE"
    

    Here's what this script does

    1. Grabs the list of stack exports using aws cloudformation list-exports
    2. Filters to just the export name and value with the --query 'Exports[*].[Name,Value]' argument
    3. Formats the exports as a string of sed replacements that will replace a token value with the actual export value with awk '{print "s/!ImportValue(" $1 ")/" $2 "/g"}' ORS='; '
    4. Finally, uses sed to make the replacements in the swagger file passed in as the first argument to the script.

    For example if you created this script as resolve-arns.sh you could then invoke it as follows:

    ./resolve-arns.sh swagger.yml > resolved-swagger.yml
    
  4. Reference the Resolved Swagger definition - Finally, you'd reference the resolved-swagger.yml file in the CF template you're using to create your API e.g. BodyS3Location: resolved-swagger.yml

CAVEATS:

  1. Of course the aws command line tools need to be available and configured
  2. This implementation isn't sophisticated enough to handle an AWS region argument... but it could be modified for that
  3. This is a bash script... if you're on a different OS like windows the same approach should work but the implementation would be different (I'd start with powershell probably...)
0
jandersen On

There's another, less complicated approach. If you use the Body property instead of the BodyS3Location of AWS::ApiGateway::RestApi you can use the Cloud Formation Instrinsic Functions to refer to or dynamically build up the ARN that you want in your swagger template.

... of course the downside is your swagger template is embedded in a CF script rather than in a separate file.

3
dinvlad On

New solution:

It is now possible to use the new AWS::Include Transform to reference the uploaded template directly from a CloudFormation template:

Api:
  Type: AWS::ApiGateway::RestApi
  Properties:
    Body:
      Fn::Transform:
        Name: AWS::Include
        Parameters:
          Location: !Sub s3://${ArtifactsBucket}/swagger.yaml

where ArtifactsBucket refers to the bucket where you upload Swagger spec before you create or update your stack. Then, in the Swagger template itself you can use the intrinsics, e.g.

x-amazon-apigateway-integration:
  type: aws_proxy
  httpMethod: POST
  uri:
    Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaFunction.Arn}/invocations

Here I'm using the long Fn::Sub notation instead of just !Sub, because Swagger doesn't natively support the latter, and also because the docs on AWS::Include Transform say that shorthand forms are not supported yet.

You can also use AWS::Serverless::Api and DefinitionBody if you use SAM.

Old workaround:

Another (somewhat hacky, yet simple) solution is to list the Api as the last resource in the CloudFormation template, and specify an empty Body with : !Sub |- at the end.

You can then concatenate this template with the actual Swagger file, and reference any parameters using the standard ${} syntax in that file.

The only minor complication is that you have to properly indent the Swagger file when concatenating it when you're using YAML (this approach won't work for JSON templates though; you'll have to substitute Body with something like jq if you use these).