AWS Get API Gateway URL for Use in Cloud Formation - amazon-web-services

I'm looking to use CloudFormation to build my AWS stack which includes an API Gateway with Usage Plans. I'd like to specify my usage plans in my main CloudFormation template, rather than having to add them as a change-set after the initial stack create. The problem is that the stack fails to create when I include the usage plan because (I think) the API Gateway is not finished deploying when it tries to create the usage plans since I get an error saying that the stage "prod" does not exist. My CloudFormation template (extract) looks like this:
Api:
Properties:
CacheClusterEnabled: true
CacheClusterSize: '0.5'
DefinitionUri: {MYS3URL}
StageName: prod
Type: AWS::Serverless::Api
ApiFreeUsagePlan:
DependsOn: Api
Properties:
ApiStages:
- ApiId:
Ref: Api
Stage: prod
Description: Free usage plan
UsagePlanName: Free
Type: AWS::ApiGateway::UsagePlan
I thought adding DependsOn: Api to the usage plan definition would work but it doesn't so I'm out of ideas?
It seems like my DependsOn statement should be on the ApiDeployment which I can see in the stack create events is still in progress when it tries to create the usage plan

The only way I've found that can do this is by setting the DependsOn property of the Usage plan to the logical Api Stage name which is {LogicalApiName}{StageName}Stage for example in my case:
Api:
Properties:
CacheClusterEnabled: true
CacheClusterSize: '0.5'
DefinitionUri: {MYS3URL}
StageName: prod
Type: AWS::Serverless::Api
ApiFreeUsagePlan:
DependsOn: ApiprodStage
I don't like this as it relies on the logical stage naming convention which I don't believe is officially documented in the AWS CloudFromation docs, however it appears to be the only reliable option

Related

How do i uncheck "Use Lambda Proxy Integration" in an API Gateway method from with SAM?

I'm trying to work my way through AWS sam and setting up a new ApiGateway. I want the lambdas to run without the 'use lambda proxy integration' setting. I've been trying for ages now, and haven't made much progress.
A simplified version of my template.yaml
Resources:
MyLambda:
Type: AWS::Serverless::Function
Properties:
#snip#
Events:
PostEvent:
Type: Api
Properties:
Path: /Some/Path
Method: Post
RestApiId:
Ref: MyApi
MyApi:
Type: AWS::Serverless::Api
Properties:
Name: Some-Api
StageName: Prod
As you can see, I haven't made any progress at all. I'm simply getting lost in the configuration here, and hoping that anyone can point me in the right direction
Sicne you have opted out of integration proxy, the request and response may need mapping.
Have you tried updating the integration response - mapping template, try below,
AWS::Serverless::Api
GatewayResponses

how can I use lambda versions and aliases to version my API with CloudFormation?

We have an API which needs to be versioned. We are using API Gateway and Lambda on AWS. All code is deployed using CICD, and we use SAM templates on top of CloudFormation.
NB we are using API Gateway stages to handle different environments (e.g. stage "dev1" points to lambda "dev1-MyService".
We have a resource 'v1' defined as follows:
V1Resource:
Type: AWS::ApiGateway::Resource
Properties:
RestApiId: !Ref Api
ParentId: !GetAtt Api.RootResourceId
PathPart: 'v1'
# proxy traffic to a lambda:
ProxyMethod:
Type: AWS::ApiGateway::Method
Properties:
HttpMethod: ANY
...
Integration:
Type: AWS_PROXY
Uri: arn:aws:apigateway:...:function:MyFunction:v1/invocations'
And a lambda defined like this:
ApiFunction:
Type: AWS::Serverless::Function
Properties:
AutoPublishAlias: "v1"
FunctionName: MyFunction
...
What we want to do is keep the v1 lambda in the system, but when we have breaking changes to the API, we can define a /v2/ resource, pointing to the v2 alias of the lambda
My plan was to define a second resource (V2Resource) and second proxy method, referencing the v2 alias of the lambda, which I could create by bumping AutoPublishAlias to v2 in the same change.
Then the v2 alias remains pointing to the old version (forever in theory) and each time we deploy from then on, the latest version of the API is aliased to v2 (until we need to do another breaking change and introduce v3).
Unfortunately, when I do that, it deletes the old alias v1.
So how am I supposed to use Lambda aliases to do API versioning with API Gateway? With CloudFormation and SAM templates?
You could use AWS::Lambda::Version resource, from the documentation:
creates a version from the current code and configuration of a function. Use versions to create a snapshot of your function code and configuration that doesn't change.
LambdaVersion:
Type: AWS::Lambda::Version
Properties:
FunctionName: !Ref ApiFunction
Description: v1
Where the Description property gets fixed because it does not accept updates. Additionally, you can create some aliases for these version like dev, stage, prod. For example, for dev version this would be
DevAlias:
Type: AWS::Lambda::Alias
Properties:
Description: Dev Alias for Lambda function
FunctionName: !Ref ApiFunction
FunctionVersion: !GetAtt LambdaVersion.Version
Name: DEV

How do you get the API endpoint's URL when it's created within the Events property of Cloudformation's Lambda definition

I'm creating a Lambda function via CloudFormation (AWS' SAM: Serverless Application Model) and have defined an API endpoint via the Lambda-function's Events property.
...
MyFunction:
Type: AWS::Serverless::Function
Properties:
Description: Do amazing things
CodeUri: my_function/
Events:
Trigger:
Type: Api
Properties:
Path: /p1/{v1}
Method: post
Handler: app.run
...
I'd now like to use the URL of the endpoint that's been created in another part of the CloudFormation YAML file. I've tried to use the SAM documentation for Lambda but the only return values have to do with the Function's ARN and resource name.
Specifically, thought I think unrelated to the exact question, I want to use the API endpoint as a subscription for an SNS Topic.
¿How can I get the URL of the API Endpoint?
you can directly reference the RestApi resource like this.
Resources:
apiGateway:
Type: AWS::ApiGateway::RestApi
Properties:
Name: !Sub ${AWS::StackName}-my-api
Description: my-api-edge
Outputs:
apiGatewayInvokeURL:
Value: !Sub "https://${apiGateway}.execute-api.${AWS::Region}.amazonaws.com/${apiGatewayStageName}"
lambdaArn:
Value: !GetAtt "lambdaFunction.Arn"
I think I found the answer across a couple of places.
This Stack Overflow post shows that there is an implicit reference created that you can use as follows
!Ref ServerlessRestApi
This was supported in practice, by a SAM Respository App
And then I re-read, more closely, the SAM API documentation which shows the RestApiId Property. It says
...Typically, this is set to reference an AWS::Serverless::Api resource defined in this template. If not defined, a default AWS::Serverless::Api resource is created..."
So it looks like you can reference it as !Ref ServerlessRestApi without any modification to the YAML in the original question, or you could add the following Property, RestApiId: MyAPI, and reference it as !Ref MyAPI.
However, to get the actual URL, it looks like you have to use a Fn::Sub to glue together a couple of parts. Pahud Hsieh does it in his SAM Repository app above
Outputs:
APIUrlPrefix:
Value:
Fn::Sub:
- https://${ServerlessRestApi}.execute-api.${Region}.amazonaws.com/Prod/incomingwebhooks/
- Region:
Ref: AWS::Region
ServerlessRestApi:
Ref: ServerlessRestApi
...

How To Rollback AWS CodeStar Lambda Functions Deployed Via CloudFormation?

I'm creating a Nodejs microservice for AWS Lambda. I scaffolded by project using AWS Codestar, and that set me up with a CI/CD pipeline that automatically deploys the lambda function. Nice.
The issue is that every time it deploys the lambda function it must delete and recreate the function, thus deleting any versions or aliases I made.
This means I really can't roll back to other releases. I basically have use git to actually revert the project, push to git, wait for the super-slow AWS Code Pipeline to flow through successfully, and then have it remake the function. To me that sounds like a pretty bad DR strategy, and I would think the right way to rollback should be simple and fast.
Unfortunately, it looks like the CloudFormation section of AWS doesn't offer any help here. When you drill into your stack on the first CloudFormation page it only shows you information about the latest formation that occurred. Dear engineers of AWS CloudFormation: if there was a page for each stack that showed a history of CloudFormation for this stack and an option to rollback to it, that would be really awesome. For now, though, there's not. There's just information about the latest formation that's been clouded. One initially promising option was "Rollback Triggers", but this is actually just something totally different that lets you send a SNS notification if your build doesn't pass.
When I try to change the CodePipeline stage for deploy from CREATE_CHANGE_SET to CREATE_UPDATE I then get this error when it tries to execute:
Action execution failed UpdateStack cannot be used with templates
containing Transforms. (Service: AmazonCloudFormation; Status Code:
400; Error Code: ValidationError; Request ID:
bea5f687-470b-11e8-a616-c791ebf3e8e1)
My template.yml looks like this by the way:
AWSTemplateFormatVersion: 2010-09-09
Transform:
- AWS::Serverless-2016-10-31
- AWS::CodeStar
Parameters:
ProjectId:
Type: String
Description: AWS CodeStar projectID used to associate new resources to team members
Resources:
HelloWorld:
Type: AWS::Serverless::Function
Properties:
Handler: index.handler
Runtime: nodejs8.10
Environment:
Variables:
NODE_ENV: staging
Role:
Fn::ImportValue:
!Join ['-', [!Ref 'ProjectId', !Ref 'AWS::Region', 'LambdaTrustRole']]
Events:
GetEvent:
Type: Api
Properties:
Path: /
Method: get
PostEvent:
Type: Api
Properties:
Path: /
Method: post
The only options in the CodePipeline "Deploy" action are these:
It would be really great if someone could help me to see how in AWS you can make Lambda functions with CodePipeline in a way that they are easy and fast to rollback. Thanks!

API Gateway with SAM isn't updated correctly

We use Cloud Formation for define a bunch of Lambda functions:
AWSTemplateFormatVersion: '2010-09-09'
Transform:
- 'AWS::Serverless-2016-10-31'
Resources:
MyLambda:
Type: 'AWS::Serverless::Function'
Properties:
Handler: com.handler::MyLambda
Runtime: java8
CodeUri: .
Description: Some desc
MemorySize: 512
Timeout: 15
Role: !Ref LambdaRole
FunctionName: MyLambda
Events:
MyLambdaEvt:
Type: Api
Properties:
RestApiId: !Ref MyApiDef
Path: /lambda/my
Method: get
MyApiDef:
Type: AWS::Serverless::Api
Properties:
DefinitionUri: s3://a-bucket/api-gateway.yml
StageName: prod
Outputs:
ApiUrl:
Description: URL of your API endpoint
Value: !Join
- ''
- - https://
- !Ref MyApiDef
- '.execute-api.'
- !Ref 'AWS::Region'
- '.amazonaws.com/prod'
A CodePipeline generate a changeset and execute it.
In this way all the Lambda function are correctly updated but the API Gateway endpoint are not update correctly and we need to import and deploy the YML in s3://a-bucket/api-gateway.yml manually.
Why the API doesn't update (an educated guess)
In order for a change to be added to a a change set, CloudFormation has to detect a change. If the only thing that changes (for MyApiDef) between deployments is the contents of the .yaml file out on S3, CloudFormation isn't going to detect a change that it needs to add to the change set.
If this API definition lived in the CF template, rather than a file on S3, CF would (obviously) detect every change and update the API for you.
Since the definition lives in S3, and the file name hasn't changed, no change is detected, so nothing gets updated.
Possible work arounds
You have to convince CloudFormation that something has changed with your API definition. These two things worked for me:
Updating the MyApiDef key itself each run works. (MyApiDefv2,
MyApiDefv3, etc)
Updating the DefinitionUri works. (i.e. version the
filename in S3).
Neither of these is great, but appending a version to the filename in S3 seems more reasonable than the other option.
There are probably other ways to convince CloudFormation a change has taken place. Notably, I could not get Variables to work for this purpose.