- Create an API instance
- Configure a stage
- Deploy API changes from swagger
Problems I Ran Into
As I attempted different appraoches, I experienced a few common problems:
- Deployment of revised swagger would not occur
- Updating the stack with revised swagger would error with “stage already exists”
- No deployment history
CloudFormation Basics
Being new to CloudFormation, I didn’t fully grok some key CloudFormation concepts which was a barrier to getting my template right. The primary concept is that CloudFormation templates dictate desired state, not a set of operations to perform. If a resource is defined in your template, it will be created. If a resource already exists, it will not be created, but can be updated if its properties change. If a resource is removed from the template it will be deleted.API Gateway Basics
- Swagger is used to define a REST API.
- That REST API is deployed.
- A stage references a deployment to make the API available via an endpoint.
The Template
--- AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: Setup our API Gateway instances Parameters: StageName: Type: String Default: 'example_stage' Description: 'The name of the stage to be created and managed within our API Gateway instance.' Resources: Api: Type: AWS::ApiGateway::RestApi Properties: Name: ExampleApi EndpointConfiguration: Types: - REGIONAL # The body should contain the actual swagger Body: $SWAGGER_DEFINITION$ # Timestamp is added so that each deployment is unique. Without a new timestamp, the deployment will not actually occur ApiDeployment$TIMESTAMP$: Type: AWS::ApiGateway::Deployment DependsOn: [ Api ] # we want to retain our deployment history DeletionPolicy: Retain Properties: RestApiId: Ref: Api ApiStage: Type: AWS::ApiGateway::Stage DependsOn: [ApiDeployment$TIMESTAMP$] Properties: RestApiId: Ref: Api DeploymentId: Ref: ApiDeployment$TIMESTAMP$ StageName: {Ref: StageName} MethodSettings: - ResourcePath: "/*" HttpMethod: "*" LoggingLevel: INFO MetricsEnabled: true DataTraceEnabled: true Outputs: Endpoint: Description: Endpoint url Value: Fn::Sub: 'https://${Api}.execute-api.${AWS::Region}.amazonaws.com'
Key Points
The deployment resource can specify a StageName and StageDescription. If this is done the deployment will implicitly create a stage that is not managed by CloudFormation. If we attempt to create a stage with the same name we’ll get an error that it already exists. Instead we’re better off creating a deployment that doesn’t prescribe anything about the stage and explicitly defining a stage resource.Each deployment needs a unique id. Otherwise, CloudFormation will determine the deployment already exists and will not create a deployment. Without a new deployment, changes to our REST API will not be visible. Furthermore, we set a deletion policy on our deployment to retain the deployment. Otherwise, when the timestamp on our deployment changes, CloudFormation will want to delete the old deployment. If this deletion occurs, the deployment is removed from our stage deployment history. With this policy, the deployment is removed from the stack but not deleted from API Gateway.
EDIT:
A script must be written to preprocess the the template and replace the $TIMESTAMP$ token with the timestamp WITHOUT any separators e.g. 05012019355 Thanks to somebody for pointing this out in the comments.
Disclaimer: I wrote this post as an enthusiast. The content is not the official position of Amazon or AWS.
This is a very good write up, can I suggest that you add a summary of it + link to https://stackoverflow.com/questions/41423439/cloudformation-doesnt-deploy-to-api-gateway-stages-on-update ?
ReplyDeleteThis does not work :
ReplyDeleteResource name ApiDeployment$TIMESTAMP$ is non alphanumeric.
This does NOT work
ReplyDeleteAll good, but where from do you have that $TIMESTAMP$ value?
ReplyDeleteI'm guessing they have something else inserting the timestamp at the time the template is generated. This does not work for me either as explicitly written out, but if you can manually update your template's stage/deployment/base path mapping resources and insert your own unique timestamp, it will properly replace the deployment resource with the new one. Here's my functional example:
ReplyDeleterDeployment05012019355:
Type: AWS::ApiGateway::Deployment
DependsOn: rApiGetMethod
Properties:
RestApiId:
Fn::ImportValue:
!Sub '${pApiCoreStackName}-RestApi'
StageName: !Ref pStageName
rCustomDomainPath:
Type: AWS::ApiGateway::BasePathMapping
DependsOn: [rDeployment05012019355] # rDeployment
Properties:
BasePath: !Ref pPathPart
Stage: !Ref pStageName
DomainName:
Fn::ImportValue:
!Sub '${pApiCoreStackName}-CustomDomainName'
RestApiId:
Fn::ImportValue:
!Sub '${pApiCoreStackName}-RestApi'
I don't understand how did you this seems sort of id '05012019355' into the resource name, are you hard-coding it?
DeleteThere is a script that preprocesses the template. This could be a shell script python, anything that can replace the $TIMESTAMP$ token with a timestamp without separators. Separators are undesirable as CF will tolerate them in a resource name.
DeleteYou are correct, we do have a script inserting the timestamp. Also, I apparently did not have notifications of comments set up.
ReplyDeletehow do I do that? do you have any example?
DeleteThannks
Hi, as asked above. Do you have any example of appending the timestamp to logical ID of api gateway deployment resource name?
DeleteThis comment has been removed by the author.
ReplyDeleteGreat article! Here is a gist I wrote to accomplish this. It should be easy to adapt this to your purposes.
ReplyDeletehttps://gist.github.com/officialhopsof/faafdde667e10cc4f9de24df92c30b0b