11

I'm using AWS CloudFormation to create an API gateway. I have a AWS::ApiGateway::Deployment resource, which works great when I create my stack. However, if I update my stack (with a change in the AWS::ApiGateway::Method, for example), the API does not get deployed again. I have to manually deploy the API in API Gateway.

Anyone know how I can automatically deploy the gateway when the stack is updated?

Marcel Gosselin
  • 4,610
  • 2
  • 31
  • 54
Manu
  • 265
  • 1
  • 3
  • 6
  • Someone from the CloudFormation team may be able to give a better explanation, but to my understanding, you will need to create a new Deployment resource in your template every time you update your stack. – RyanG Jul 14 '16 at 17:49
  • See also this question for reference https://stackoverflow.com/q/41423439/227821 – lony Oct 22 '21 at 18:42

7 Answers7

7

I put a date/timestamp in my description to force a redeploy every time the templates are updated. This is pretty easy for me since I use Troposphere and seems to do the trick. Alternatively you could pass in a date/timestamp as a parameter.

martin
  • 119
  • 6
  • 5
    This is not working anymore. Changing description of AWS::ApiGateway::Deployment resource just updates the deployment's description field and it is not creating a new one. The only way is to create the deployment using a Lambda backed custom resource. – Cagatay Gurturk Oct 29 '16 at 17:59
  • @ÇağatayGürtürk is correct. There's a relevant SO post [here](https://stackoverflow.com/questions/41423439/cloudformation-doesnt-deploy-to-api-gateway-stages-on-update) with more information. **Note the comments in the accepted answer, though.** It appears that AWS has suggested a different solution. Still waiting to hear from OP on that article as to what they'd suggest. – John T Jul 14 '17 at 14:59
2

You can have your own Stage and Deployment resources, where the DeploymentId in the Stage resource refers to the deployment resource, and then you can define a dynamic name to the deployment resources using methods like appending timestamp to it.

This forces a re-deploy everytime.

2

I may be late, but here are the options which which you do a redeployment if a API resources changes, may be helpful to people who still looking for options -

  1. Try AutoDeploy to true. If you are using V2 version of deployment. Note that you need to have APIGW created through V2. V1 and V2 are not compatible to each other. https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-stage.html#cfn-apigatewayv2-stage-autodeploy

  2. Lambda backed custom resource, Lambda inturn call createDeployment API - https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html

  3. CodePipeline that has an action that calls a Lambda Function much like the Custom Resource would - https://docs.aws.amazon.com/codepipeline/latest/userguide/actions-invoke-lambda-function.html

  4. SAM(Serverless Application Model) follows a similar syntax to CloudFormation which simplifies the resource creation into abstractions and uses those to build and deploy a normal CloudFormation template. https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html

  5. If you are using any abstraction layer to cloudformation like Sceptre, you can have a hook to call createDeployment after any-update to the resource https://sceptre.cloudreach.com/2.3.0/docs/hooks.html

I gone with third option since I kept using Sceptre for Cloudformation deployment. Implementing hooks in sceptre is easy as well.

Athi
  • 347
  • 4
  • 12
  • I have my APIGateway created with V1. How can I transition it to V2? I want to use the `AutoDeploy` attribute. – amulya349 May 06 '22 at 10:03
1

*** This is a repost from the linked question. ***

If you have something to do $TIMESTAMP$ replacement in your resource names, I'd probably go with that as it's cleaner and you don't have to do any manual API Gateway management.

I have found that the other solutions posted here mostly do the job with one major caveat - you can't manage your Stage and Deployment separately in CloudFormation because whenever you deploy your API Gateway, you have some sort of downtime between when you deploy the API and when the secondary process (custom resource / lambda, code pipeline, what have you) creates your new deployment. This downtime is because CloudFormation only ever has the initial deployment tied to the Stage. So when you make a change to the Stage and deploy, it reverts back to the initial deployment until your secondary process creates your new deployment.

*** Note that if you are specifying a StageName on your Deployment resource, and not explicitly managing a Stage resource, the other solutions will work.

In my case, I don't have the $TIMESTAMP$ replacement piece, and I needed to manage my Stage separately so I could do things like enable caching, so I had to find another way. So the workflow and relevant CF pieces are as follows

  • Before triggering the CF update, see if the stack you're about to update already exists. Set stack_exists: true|false

  • Pass that stack_exists variable in to your CF template(s), all the way down to the stack that creates the Deployment and Stage

  • The following condition:

Conditions:
  StackExists: !Equals [!Ref StackAlreadyExists, "True"]
  • The following Deployment and Stage:
  # Only used for initial creation, secondary process re-creates this
  Deployment:
    DeletionPolicy: Retain
    Type: AWS::ApiGateway::Deployment
    Properties:
      Description: "Initial deployment"
      RestApiId: ...

  Stage:
    Type: AWS::ApiGateway::Stage
    Properties:
      DeploymentId: !If
        - StackExists
        - !Ref AWS::NoValue
        - !Ref Deployment
      RestApiId: ...
      StageName: ...
  • Secondary process that does the following:
# looks up `apiId` and `stageName` and sets variables

CURRENT_DEPLOYMENT_ID=$(aws apigateway get-stage --rest-api-id <apiId> --stage-name <stageName> --query 'deploymentId' --output text)
aws apigateway create-deployment --rest-api-id <apiId> --stage-name <stageName>
aws apigateway delete-deployment --rest-api-id <apiId> --deployment-id ${CURRENT_DEPLOYMENT_ID}
Mike DeMille
  • 342
  • 1
  • 5
  • 17
0

In our case using the above suggested answer to add the current timestamp to the name/description to force a new deployment was not a good solution because then the Api invoke Url would be different each deployment.

This is a problem when all of our Api customers are hooked up to & using the previous invoke Url, We don't want to update our app / force our customers to update this Url every deployment..

Here's our solution:

1) Add an output for the deployed rest-api-id something like this:

Outputs:
  OutputApiId:
    Value: 
      Ref: NameOfMyApiHere
    Description: Api generated Id
    Export: 
      Name: OutputApiId

Note: NameOfMyApiHere is type "AWS::ApiGateway::RestApi" in our case.

2) Perform cloudformation deployment/changes ( as you would normally )

3) Use the 'OutputApiId' value output from cloud formation in the AWS CLI command like so:

aws apigateway create-deployment --rest-api-id {{ OutputApiId }} --region {{my-region}}

We use ansible as our deployment tool to do the work of the cloudformation steps & capturing the returned outputs - to in turn call the AWS CLI command but I'm sure there are other options.

See the AWS CLI documentation for deploying a Rest Api here https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-deployments.html

Mitchb
  • 159
  • 3
  • 13
0

What I did so I can pass the deployment into multiple templates behind a nest parent is to use multi stages (2) and force to use a diff stage in each deployment:

aws cli in a script...

apideployment=$(aws cloudformation describe-stacks \
  --stack-name my-api-stack-name \
  --region ${awsregion} \
  --query "Stacks[0].Parameters[?ParameterKey=='APIDeploymentFlip'].ParameterValue" \
  --output text 2> /dev/null)
# revert the stages in AGW
if [ "$apideployment" = "A" ]
then
  # if A alternate with B
  deployment='B'
else
  # if B alternate with A otherwise defaults to A
  deployment='A'
fi


aws cloudformation deploy \
    --template-file $tempdir/template.yaml \
    --stack-name my-api-stack-name \
    --no-fail-on-empty-changeset \
    --parameter-overrides \
        APIDeploymentFlip=${deployment}

inside the template or templates...

Parameters:
  APIDeploymentFlip:
    Type: String
    AllowedValues:
      - A
      - B
Conditions:
  IsDeploymentA:
    Fn::Equals: [Ref: APIDeploymentFlip, "A"]
  IsDeploymentB:
    Fn::Equals: [Ref: APIDeploymentFlip, "B"]
Resources:
  AGWDeploymentA:
    Type: AWS::ApiGateway::Deployment
    Condition: IsDeploymentA
    Properties:
      RestApiId: !Ref AGWRestApi
  AGWDeploymentB:
    Type: AWS::ApiGateway::Deployment
    Condition: IsDeploymentB
    Properties:
      RestApiId: !Ref AGWRestApi

  AGWStageA:
    Type: AWS::ApiGateway::Stage
    Condition: IsDeploymentA
    Properties:
      StageName: !Sub '${APIDeploymentFlip}-stage1'
      DeploymentId: !Ref AGWDeploymentA
      RestApiId: !Ref AGWRestApi

  AGWStageB:
    Type: AWS::ApiGateway::Stage
    Condition: IsDeploymentB
    Properties:
      StageName: !Sub '${APIDeploymentFlip}-stage2'
      DeploymentId: !Ref AGWDeploymentB
      RestApiId: !Ref AGWRestApi

example stage references in other resources with If:

  AGWUsagePlan:
    Type: AWS::ApiGateway::UsagePlan
    Properties:
      ApiStages:
        - ApiId: !Ref AGWRestApi
          Stage: !If [IsDeploymentA, !Ref AGWStageA, !Ref AGWStageB]

Hope it helps others as this part of apigatewayv1 (rest) is disappointing.

-1

This is how I went about it in almost pure sls. You will need to have the aws cli tool and the json parser jq installed. It will only work on UNIX/Linux.

First using these plugins:

plugins:
  - serverless-plugin-scripts
  - '@anttiviljami/serverless-stack-output' 

Configured as such:

  output:
    file: ./config.json
  scripts:
    hooks:
      'after:aws:deploy:finalize:cleanup': jq < config.json .myRestAPI | xargs -I%% aws --profile ${self:custom.stage} apigateway create-deployment --rest-api-id %% --stage-name ${self:custom.stage} --description 'Deployment ${sls:instanceId}'

Just be sure to output the API rest id in the same stack like so (the value does not need to be exported):

  myRestAPI:
    Value:
      Ref: myApiGatewayRestAPI

Note that my stage name matches my AWS configuration profile name, you may need to update "--profile ${self:custom.stage}" to suit.

aaa90210
  • 11,295
  • 13
  • 51
  • 88