How can I put condition in serverless.yml file? - amazon-web-services

I am using serverless framework to deploy api gateway. I don't want to apply VPC to every stage in serverless. Is there a way for me to add below configuration based on stage name?
provider:
name: aws
runtime: nodejs12.x
...
endpointType: PRIVATE
vpcEndpointIds:
- 'Fn::ImportValue': 'api-gateway-endpoint'
resourcePolicy:
- Effect: Deny
...

There are a few ways to do conditional deployments in serverless.yml, some are more brittle than others and there are pros/cons to each but here is a list of methods I have collected:
Variables to control conditionals
NOTE: We use a custom regex variable syntax to separate Serverless variables from cloudformation variable syntax. All of the following snippets use this modified syntax:
provider:
name: aws
# Changes serverless variable to ${{}} double curly braces
variableSyntax: "\\${{([ ~:a-zA-Z0-9._\\'\",\\-\\/\\(\\)]+?)}}"
Serverless Custom Variables
custom:
scheduleEnabled:
dev: true
stage: true
prod: true
# Then In lambda function declaration
events:
- schedule:
name: MyScheduleName
description: SomeDescription
rate: cron(0/5 * * * ? *)
# Use custom variable and the serverless `stage` supplied
# via your deployment command to choose whether this feature is enabled
enabled: ${self:custom.scheduleEnabled.${self:custom.stage}}
input: {"_keepwarm": true}
Using Conditional Operations
Setting up CloudFormation Conditionals
resources:
- Conditions:
# True if they are equal ==
MyCondition: [!Equals ["${{env:SOMETHING}}","SOME_STRING"]]
# True if they are not equal !=
MyConditionTwo: !Not [!Equals ["${{env:SOMETHING_ELSE}}","SOME_OTHER_STRING"]]
# Using a custom serverless variable
IsProd: [!Equals ["${{self:provider.stage}}","prod"]]
Using Cloudformation Conditionals
If your conditional has two options, you can write it as:
# If true choose X, if False choose Y
Source:
Type: !If
- MyCondition # Conditional Name
- GITHUB # If condition is true
- GITHUB_ENTERPRISE # if condition is false
If your conditional is something you want to toggle ON or OFF, you can write it as:
# If True do nothing, If False choose X
MyCodebuildSetup:
Type: "AWS::CodeBuild::Project"
VpcConfig: !If
- MyCondition # Condition Name
- !Ref AWS::NoValue # If True, AWS will not attach a VPC Config
- VpcId: <My_VPC_ID> # If False, Use this VPC Config
Subnets:
- <my_VPC_Subnet>
SecurityGroupIds:
- <my_VPC_security_group
Weird Conditional Hacks (document usage well if you decide to implement them)
Conditionally deploying a large series of resources
Using Serverless to selectively deploy entire files of resources using strings set via environment variables.
resources:
- ${{file(some_dir/conditional_file_${{env:MY_CONDITION}}.yml)}}
This is also a way to toggle deployment of many resources via conditionals.
conditional_file_A.yaml can contain all the resources you toggle deployment of, while conditional_file_B.yaml can contain an empty Resources list (If you don't want a "file not found" warning from serverless:
Resources:
Using Serverless Variables in buildspec.yml files or .json files that will be sent to cloudformation (parameter store files, etc)
If you want to conditionally modify a .json or .yml file that will be sent deployed as a value for some CloudFormation key (such as deploying a .json file for parameter store, or submitting buildspec.yml files for CodePipeline), you can't actually use ${} serverless syntax in these external files. This is because serverless gets confused with it's own method of calling Key/Values from external files.
In order to use Serverless Variables in these files, you can make them .txt extensions as long as the actual file is formatted as proper .json or .yml
Source:
Type: CODEPIPELINE
# This file can't have any serverless variables in it
BuildSpec: ${{file(my_buildspec_file.yml)}}
Source:
Type: CODEPIPELINE
# This file CAN have serverless variables because it is
# interpreted by serverless as a .txt, the variables
# are resolved and then it is sent to cloudformation as a string anyway
BuildSpec: ${{file(my_buildspec_file_yaml_formatted.txt)}}

You may check the following answers: Conditional serverless.yml based on stage?
Therefore, you get something like:
resources:
Conditions:
IsProd:
Fn::Equals:
- ${opt:stage}
- prod
Resources:
SomeIAMRole:
Type: AWS::IAM::Role
Condition: IsProd
Properties:
etc
etc

Alternatively, there is a Serverless Plugin Ifelse
Add the plugin:
plugins:
- serverless-plugin-ifelse
and then add your conditions:
custom:
.....
....
serverlessIfElse:
- If: '"${opt:stage}" == "production"'
Set:
functions.helloWorld.cors: true
see more info Serveless plugin if-else

Related

using ternary operator in a string for serverless yaml

I'm using serverless for AWS deployments and have an issue where we have named one of our Elastic Search resources "prod-logs" on production and "nonprod-logs" on UAT. I have the variable ${opt:stage} available in my serverless.yml to tell if something is on prod or UAT.
How can I use ${opt:stage} within my iamRoleStatements to be able to output arn:aws:es:${aws:region}:${aws:accountId}:domain/nonprod-logs/* or arn:aws:es:${aws:region}:${aws:accountId}:domain/prod-logs/* based on the environment? Or more specifically, how do I omit non when ${opt:stage} is prod?
my yaml looks like this:
provider:
iamRoleStatements:
- Effect: Allow
Resource: arn:aws:es:${aws:region}:${aws:accountId}:domain/nonprod-logs/*
Action:
- es:ESHttpDelete
I've seen that you can use a ternary operator, but i'm not sure how you might use it in the middle of a string like:
Resource: arn:aws:es:${aws:region}:${aws:accountId}:domain/${!e ${opt:stage} = 'prod'? '': 'non'}prod-logs/*

Dynamically change event properties on aws cloudformation templates

We are building a serverless app using aws and we want to enable lambda warmers only on the production environment.
Our cloudformation parameters:
Parameters:
Environment:
Description: Environment name
Type: String
EnableWarmer:
Description: Flag to enable/disable warmup Events
Default: DISABLED
Type: String
Our lambdas yaml file looks like this:
MyLambda:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub '${Environment}-my-lambda'
CodeUri: src
Handler: lambda.handler
Runtime: nodejs12.x
MemorySize: 128
Timeout: 100
Description: Creates a new something
Layers:
- !Ref InternalDependencyLayer
- !Ref ExternalDependencyLayer
Role: !Ref LambdaRoleArn
Events:
Api:
Type: Api
Properties:
Path: /url
Method: POST
RestApiId: !Ref ApiGateway
WarmingSchedule:
Type: Schedule
Properties:
Enabled: !Ref EnableWarmer
Schedule: rate(5 minutes)
Input: '{ "warmer":true, "concurrency": 2 }'
And then we deploy the dev environment with these params:
- Key: Environment
Value: dev
- Key: EnableWarmer
Value: DISABLED
Similarly for the production environment we deploy with these params:
- Key: Environment
Value: production
- Key: EnableWarmer
Value: ENABLED
According to aws documentation parameters can't be of type boolean which is the required type of the enabled attribute of the schedule event.
Fortunately amazon states:
Enabled Indicates whether the rule is enabled.
To disable the rule, set this property to False.
Type: Boolean
Required: No
AWS CloudFormation compatibility: This property is similar to the
State property of an AWS::Events::Rule resource. If this property is
set to True then AWS SAM passes ENABLED, otherwise it passes DISABLED
https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-function-schedule.html
However when we deploy to the dev environment the warmers are enabled.
The problem with the Enabled property here seems to be that it does not support using CloudFormation's If function and a Condition to do something like that:
WarmingSchedule:
Enabled: !If [WarmerCondition, true, false]
(At least I couldn't figure it out, but maybe I just did something wrong) This is similar to what #raul-barreto suggests with !Equals. However, in my tests it always sets the rule's status to ENABLED.
It seems like there are two options left what you can do:
a) (Not recommended) Copy the CloudFormation code of your Lambda function and have a second one available in your CloudFormation resources. Now set a condition to both of them so you only create one of them, depending on your environment. Note: I don't say this is a good approach but it'd be possible. Problems are you have to maintain the definition of both Lambda functions and if you repeat this approach, you'll faster reach CloudFormations template size limits.
b) If you want to make sure you have a minimum number of instances available to your function (at least that's what I interpret from your question), consider using AWS Lambda's Provisioned Concurrency feature instead. You can define it like this in your AWS Lambda function using SAM:
MyLambda:
Type: AWS::Serverless::Function
Properties:
AutoPublishAlias: 'LATEST' #Setting AutoPublishAlias is important, otherwise you can not use provisioned concurrency
ProvisionedConcurrencyConfig:
ProvisionedConcurrentExecutions: 2
This snippet will provision concurrency for the function's alias 'LATEST' which points to the latest version of your function. Read more about AutoPublishAlias in SAM (Note: the link's content is written in the context of traffic shifting but it explains what the property does). It makes sure that you always have 2 instances of your Lambda functions available.
The accepted solution works, but as stated in comments, it is a bit difficult to control the enabling and disabling of the event. The following seems to work in a more robust way.
Events:
Scheduler:
Type: Schedule
Properties:
Schedule: !If [EnableWarmer, "rate(5 minutes)",
"cron(00 00 01 01 ? 1970)"]
According to the documentation, Enabled must a Boolean variable.
You can still have a String parameter and convert it to Boolean inside the CloudFormation.
WarmingSchedule:
Type: Schedule
Properties:
Enabled: !Equals [!Ref EnableWarmer, ENABLED]
Schedule: rate(5 minutes)
Input: '{ "warmer":true, "concurrency": 2 }'
This way you still can send ENABLED or DISABLED as a parameter but the input of WarmingSchedule.Enabled will be a Boolean.

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

AWS Serverless, CloudFormation : Error, Trying to populate non string value into a string for variable

I am using serverless framework for deploying my application on AWS Cloud.
https://serverless.com/
I want to use the value of AWS Account ID in my serverless.yml file and I want to export the acccount ID as the environment variable, so that it can be accessed from Lambda functions.
Based on the value of this lambda function, I want to create some resources ( like IAM roles, etc.), which refer to this accountId variable.
But when I try to deploy the stack, I get the below error,
Trying to populate non string value into a string for variable
${self:custom.accountId}. Please make sure the value of the property
is a string.
My Serverless.yml file is as below
custom:
accountId : !Ref "AWS::AccountId"
provider:
name: aws
runtime: go1.x
stage: dev
region: us-east-1
environment:
ACCOUNT_ID : ${self:custom.accountId}
myRoleArn: arn:aws:iam::${self:custom.accountId}:role/xxxxxxxx
Is there any way to refer to the value of the Account Id in the serverless.yml file?
You can't reference AWS::AccountId in your serverless.yml, because it doesn't quite translate when creating the CloudFormation template.
The workaround is to use the serverless plugin Pseudo Parameters.
You can install the plugin using npm.
npm install serverless-pseudo-parameters
You will also need to add the plugin to the serverless.yml file.
plugins:
- serverless-pseudo-parameters
Then you can reference your AccountId with #{AWS::AccountId}
functions:
helloWorld:
handler: index.handler
events:
- http:
path: /
method: get
environment:
ACCOUNT_ID : #{AWS::AccountId}
Note that the reference begins with a hash instead of a dollar sign.

AWS Athena deployment serverless framework

has somebody a hint how I have to transform !Ref und !Sub from CloudFormation into serverless.yml.
resources:
Resources:
AthenaCreateDatabaseQuery:
Type: 'AWS::Athena::NamedQuery'
Properties:
Description: Run this query to initialize the Athena database
QueryString: "CREATE DATABASE IF NOT EXISTS $(self:custom.etlDatabase};"
Database: ${self:custom.etlDataBase}
In Cloudformation the QueryString Property starts with !Sub and
the Database Property with !Ref.
Thanks Christian
!Sub isn't currently supported natively by serverless.com (see this issue on GitHub) but you can use the following plugin https://gitlab.com/kabo/serverless-cf-vars
Whenever you want the cloudformation template to have a string that contains ${}, simply use #{} instead, and it will get transformed into correct ${} (with Fn::Sub inserted for you) in the cloudformation template before deployment.
Or use a custom variable syntax as suggested here. Both require the use of Fn::Sub and Fn::Ref in yaml instead of the short form !Sub and !Ref