Multiple SAM Templates with Same Custom Domain, Different Path Mapping - amazon-web-services

I'm trying to create multiple services with different repositories; actors and movies. Each of these repositories have their own template.yaml
Unfortunately if I use the same domain but different base path in both of these template.yaml, I have errors and 2nd service is not deployed;
in actors repository; template.yaml
Mapping:
Type: AWS::ApiGateway::BasePathMapping
Properties:
BasePath: actors
DomainName: api.example.com
RestApiId: !Ref Api
Stage: !Ref Env
Api:
Type: AWS::Serverless::Api
Properties:
# ... other props
in movies repository; template.yaml
Mapping:
Type: AWS::ApiGateway::BasePathMapping
Properties:
BasePath: movies
DomainName: api.example.com
RestApiId: !Ref Api
Stage: !Ref Env
Api:
Type: AWS::Serverless::Api
Properties:
# ... other props
It looks like this can't be done. Even if I move the domain configuration within Api resource definition it says the domain is being used in another stack as the stack names are also different.
Is there any way around this?

You cannot create two resources with identical domain names. This would allow for very bad things.
Instead,
Option one 1 : Single AWS::Serverless::Api
Create one AWS::Serverless::Api resource with a single domain name and multiple Events
Option 2 : Multiple AWS::Serverless::Api
Create multiple AWS::Serverless::Api resources with unique domain names (use the default one at creation)
create a single AWS::CloudFront::Distribution resource with multiple Origins that are your AWS::Serverless::Api's and the domain name you would like it to use. This way it will "front" that domain to your many origins (api-gateways)
Notes:
You may run into some syntax issues should you decide to continue to use multiple template files, I'd recommend using a nested approach

#Revenant this seems to be possible now. Your way doing it sound very legic right now.
Having a separate API per service
Having a separate basepath per service
api.example.com/actors
api.example.com/moviews
Just make sure your service supports to be called via the root path /

Related

How to solve a circular dependency in SAM Docs while putting API endpoint in the lambda function's environment variables

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: hello
Resources:
ApiGatewayApi:
Type: AWS::Serverless::Api
Properties:
StageName: stage
TracingEnabled: true
FunctionA:
...
Environment:
Variables:
TEST: !Ref ApiGatewayApi
Events:
GetUsers:
Type: Api
Properties:
Path: /account
Method: get
RestApiId:
Ref: ApiGatewayApi
FunctionB:
...
Environment:
Variables:
API_URL: !GetAtt ApiGatewayApi.RootResourceId
Events:
OrderEvent:
Type: SQS
Properties:
Queue: !GetAtt OrderServiceQueue.Arn
This leads to a circular dependency. IF I do !Ref in a function that does not have an event with API, it does not complain about it. I read the premium support article from aws, blogs and other stack overflow questions but they are not similar to my question.
FunctionB successfully refers to the API gateway id while FunctionA does not.
I create the api outside the function, so I think it SHOULD !Ref the endpoint in it. Is there something else?
The circular reference is created by how AWS SAM uses the events in order to create the definition of the API. This basically means that it needs the ARNs of the lambda functions to construct this definition before it can create the API. But since you are needing IDs of the API in order to create the lambda, you end up with a circular reference since neither can be created without the other one already existing.
The first way to solve this problem is by deploying your stacks in multiple steps. You could first deploy an empty API, which would allow you to reference the API IDs when adding the lambdas. The significant drawback of this approach is of course if you want to easily replicate this stack on another account or redeploying the API for some reason, which means you'd have to use this trick again each time.
Another way, if you really want to have this value as an environment variable, would be to manually create the definition body for the API (in which you construct the ARNs of the lambda, not reference them) and presumably, you'll also need to manually create the permissions in order to allow your API Gateway resource to execute the lambda functions.
However, a better way I feel would be to use the lambda proxy integration (which is used by default I think, but I could not find any documentation to verify this). When using the lambda proxy integration, the incoming event in lambda contains all the information about the API. You could easily extract the information you need from that event instead of having it as an environment variable, but this depends on your exact use case.

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 can I split the appsync into multiple stack?

I have a project to deploy a appsync API using this plugin (https://github.com/sid88in/serverless-appsync-plugin). And I am looking for a solution to split all infra. into multiple stacks (multiple serverless.yml file).
My project structure looks like:
main/serverless.yml
dataSources/serverless.yml
resolvers/serverless.yml
schema/serverless.yml
The main folder only deploys the Appsync instance and logging and authentication. It doesn't include any schema, resolvers etc.
And other folders each of which is to deploy schema, resolvers, dataSources to the Appsync deployed by the main folder. In these folders, they need to import the appsync infra in order to attach these resolvers.
That means there will be multiple cloudformation stacks created and using cross stack reference among them. I wonder how I can make this by using this plugin.
In general you can export CF variables that you might need in other stacks using Output such as:
resources:
Resources:
NotesTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: notes
# ...
Outputs:
Value:
Ref: NotesTable
Export:
Name: NotesTableName
And in another file read them like: 'Fn::ImportValue': NotesTableName
I am using examples from a wonderful source:
https://serverless-stack.com/chapters/cross-stack-references-in-serverless.html
Your example is AppSync...
You can define your data sources such as DynamoDb in a dedicated stack and in the AppSync sources only need to reference them by name such as:
- type: AMAZON_DYNAMODB
name: ItemSource
description: Item table
config:
tableName: ItemTable // Whatever name you used in your DynamoDb stack
For lambdas you would use the Output/import. In resolvers:
- type: AWS_LAMBDA
name: SomeLambdaSource
config:
functionName: someSource
lambdaFunctionArn:
Fn::ImportValue: SomeLambdaSource
In your lambda stack:
functions:
someSource:
name: SomeSource
handler: src/handlers/someSource.handler
resources:
Outputs:
SomeSource:
Value:
Fn::GetAtt:
- SomeSource
- Arn
Export:
Name: SomeSource

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.

AWS Get API Gateway URL for Use in Cloud Formation

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