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,
..,
..
}
}
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).
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.
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.
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...
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"
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"
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
- Grabs the list of stack exports using
aws cloudformation list-exports
- Filters to just the export name and value with the
--query 'Exports[*].[Name,Value]'
argument
- 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='; '
- 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
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:
- Of course the
aws
command line tools need to be available and configured
- This implementation isn't sophisticated enough to handle an AWS region argument... but it could be modified for that
- 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...)