I want to create an Elastic Beanstalk using CloudFormation template. I want to define an environment variable ENV_VAR_1 and set it's value to value of template parameter var1. But don't want ENV_VAR_1 to exist at all if var1 is an empty string. I.e. I don't want ENV_VAR_1 with no value.
First I tried the Conditions, but I get "Encountered unsupported property Condition" during creation of ElasticBeanstalkEnvironment resource.
Parameters:
var1:
Type: String
Conditions:
isVar1Empty: !Equals [ !Ref var1, "" ]
Resources:
ElasticBeanstalkEnvironment:
Type: 'AWS::ElasticBeanstalk::Environment'
Properties:
OptionSettings:
- Namespace: 'aws:elasticbeanstalk:application:environment'
Condition: isVar1Empty
OptionName: ENV_VAR_1
Value: !Ref var1
Then I tried AWS::NoValue
Parameters:
var1:
Type: String
Resources:
ElasticBeanstalkEnvironment:
Type: 'AWS::ElasticBeanstalk::Environment'
Properties:
OptionSettings:
- Namespace: 'aws:elasticbeanstalk:application:environment'
OptionName: ENV_VAR_1
Value: !If [[!Equals [ !Ref var1, "" ]], !Ref 'AWS::NoValue', !Ref var1]
and many permutation combinations of this. With the same result: When var1 is empty, Elastic Beanstalk gets created with ENV_VAR_1 set to ""
Conditions are going to be applied at the Resource level...currently, you cannot apply a condition to a specific property.
What you could do to satisfy these exact requirements (and this is a bit ugly), is create two conditions, one negating the other. Then with these two conditions, have them conditionally create the specific resource.
Parameters:
var1:
Type: String
Conditions:
isVar1Empty: !Equals [ !Ref var1, "" ]
isVar1NonEmpty: !Not [ !Equals [ !Ref var1, "" ] ]
Resources:
ElasticBeanstalkEnvironmentWithVar1:
Type: 'AWS::ElasticBeanstalk::Environment'
Condition: isVar1NonEmpty
Properties:
OptionSettings:
- Namespace: 'aws:elasticbeanstalk:application:environment'
OptionName: ENV_VAR_1
Value: !Ref var1
ElasticBeanstalkEnvironmentWithoutVar1:
Type: 'AWS::ElasticBeanstalk::Environment'
Condition: isVar1Empty
Properties:
OptionSettings:
- Namespace: 'aws:elasticbeanstalk:application:environment'
Like I said...a bit ugly. Note that this will only really work well if you have one or two variables like this. As soon as you add a second or third 'optional' parameter, this quickly starts spiraling out of control.
A better option might be to generate your CloudFormation template using a templating library like mustache.
Another workaround to handle conditions at option level:
Conditions:
CreateProdResources: !Equals [!Ref Env, "prod"]
EBEnvironment:
Type: AWS::ElasticBeanstalk::Environment
Properties:
OptionSettings:
- Namespace : "aws:elasticbeanstalk:command"
OptionName: Timeout
Value : 1200
- Namespace : !If [CreateProdResources, "aws:elbv2:listener:443", "aws:elasticbeanstalk:command"]
OptionName: !If [CreateProdResources, Protocol, Timeout]
Value : !If [CreateProdResources, HTTPS, 1200]
- Namespace : !If [CreateProdResources, "aws:elbv2:listener:443", "aws:elasticbeanstalk:command"]
OptionName: !If [CreateProdResources, SSLPolicy, Timeout]
Value : !If [CreateProdResources, "ELBSecurityPolicy-2016-08", 1200]
- Namespace : !If [CreateProdResources, "aws:elbv2:listener:443", "aws:elasticbeanstalk:command"]
OptionName: !If [CreateProdResources, SSLCertificateArns, Timeout]
Value : !If [CreateProdResources, !Ref ACMCertificate, 1200]
Repeated options are considered only once in Elastic Beanstalk.
Related
I'm facing an issue parsing SSM parameters from a root stack into a child stack. When creating the stack, the first stack resource fails with "
Unable to fetch parameters [value1,value2,value3,value4] from parameter store for this account."
However, the value of the parameters is fetched. The values are strings too, which are supported by CloudFormation. Also, when using the same template on independent stacks, the stacks are deployed as intended. The values of the parameters in SSM are like this: subnet1,subnet2 hence the need to split the values afterwards during ALB creation.
Below are the stacks.
root template
Parameters:
PublicSubnetAZ:
Type: AWS::SSM::Parameter::Value<String>
Default: 'PublicSubnetAZ'
Description: "Public Subnet AZs"
AppSubnetAZ:
Type: AWS::SSM::Parameter::Value<String>
Default: 'AppSubnetAZ'
Description: "App Subnet AZs"
Resources:
LBStack:
Type: "AWS::CloudFormation::Stack"
Properties:
TemplateURL: "https://s3bucket.s3.eu-west-1.amazonaws.com/load_balancing.yaml"
Parameters:
PublicSubnetAZ: !Ref PublicSubnetAZ
AppSubnetAZ: !Ref AppSubnetAZ
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-lb"
nested template
Parameters:
PublicSubnetAZ:
Type: String
AppSubnetAZ:
Type: String
Resources:
LoadBalancerExternal:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: !Join ['-', [!Ref AWS::StackName, "external"]]
Scheme: "internet-facing"
Type: "application"
Subnets:
- !Select [0, !Split [',', !Ref PublicSubnetAZ]]
- !Select [1, !Split [',', !Ref PublicSubnetAZ]]
SecurityGroups:
- '{{resolve:ssm:ExternalLoadBalancerSecurityGroup}}'
IpAddressType: "ipv4"
LoadBalancerInternal:
Type: "AWS::ElasticLoadBalancingV2::LoadBalancer"
Properties:
Name: !Join ['-', [!Ref AWS::StackName, "internal"]]
Scheme: "internal"
Type: "application"
Subnets:
- !Select [0, !Split [',', !Ref AppSubnetAZ]]
- !Select [1, !Split [',', !Ref AppSubnetAZ]]
SecurityGroups:
- '{{resolve:ssm:/InternalLoadBalancerSecurityGroup}}'
IpAddressType: "ipv4"
Any ideas?
I'm trying to find a way to use ImportValue inside If function but can't find a proper syntax. Any help is appreciated.
Below the code I'm trying:
SomeTaskdefinition:
Type: AWS::ECS::TaskDefinition
Properties:
Family: 'FamilyName'
ContainerDefinitions:
- Name: ContainerName
Image: 'imagename:net/v2/'
Environment:
- Name: ENV_VARIABLE_1
Value:
Fn::If:
Fn::Equals:
Fn::ImportValue:
!Sub "${ImportStackname}-ECSCluster"
''
'notpresent'
'present'
I come across here with similar problem.. my idea was to either be able specify DatabaseHost as parameter, if left empty - value should be taken from DatabaseStack export. Here is my sample code - it uses !ImportValue inside !If function. You will get the idea (instead of constructing only Value - construct whole Name Value list object)
Conditions:
DatabaseHostPresent: !Not [ !Equals [ !Ref DatabaseHost, ""]]
Resources:
...
ContainerDefinitions:
- Name: !Sub ${ApplicationName}-web-${EnvironmentName}
Environment:
- !If
- DatabaseHostPresent
- Name: DB_HOST
Value: !Ref DatabaseHost
- Name: DB_HOST
Value: !ImportValue
Fn::Sub: ${DatabaseStack}-EndpointAddress
I don't believe this is possible. You cannot use ImportValue inside an Equals function.
Another way to work around this is to use a nested template:
Add a Parameter to the Child template
In the Parent template, use the Import to populate the Parameter
In the Child template, you can do everything you'd want including using it in the Conditions section
Untested example:
# in Parent
Resources:
ChildStack:
Type: 'AWS::CloudFormation::Stack'
Properties:
Parameters:
Stackname: {'Fn::ImportValue': !Sub "${ImportStackname}-ECSCluster"}
TemplateURL: './child.yaml'
# in Child
Parameters:
Stackname:
Type: String
Default: ''
Conditions:
HasStack: !Not [!Equals [!Ref Stackname, '']]
Resources:
SomeTaskdefinition:
Type: AWS::ECS::TaskDefinition
Properties:
Family: 'FamilyName'
ContainerDefinitions:
- Name: ContainerName
Image: 'imagename:net/v2/
Environment:
- Name: ENV_VARIABLE_1
Value: !If [HasStack, 'present', 'notpresent']
Using a combination of intrinsic function I've obtained the result:
parameter1: !If
- condition1
- !ImportValue
Fn::Sub: "${name}-dev-parameter"
- AWS::NoValue
Make sure to use the sequence of intrinsic function as I've listed. It seems to be the only way.
I want to give this resource 2 security groups that exist outside the stack, plus one that was created as part of the stack...
I have tried the below and received the error:
Value of property SecurityGroups must be of type List of String
SecurityGroups:
- !FindInMap [ envMap, !Ref env, securityGroups ]
- !GetAtt SG.GroupId
for reference, here is my map
Mappings:
envMap:
qa:
"securityGroups":
- sg-xxxxxxxx
- sg-yyyyyyyy
and here is the resource
LoadBalancer:
Type: 'AWS::ElasticLoadBalancingV2::LoadBalancer'
Properties:
Name: !Join
- '-'
- - 'OR'
- 'ALB'
- !Ref env
Scheme: internal
SecurityGroups: !FindInMap [ envMap, !Ref env, securityGroups ]
Subnets: !FindInMap [ envMap, !Ref env, subnets ]
Type: application
IpAddressType: ipv4
EDIT: here is my fixed code
"securityGroups": 'sg-xxxxxx,sg-yyyyyy'
LoadBalancer:
Type: 'AWS::ElasticLoadBalancingV2::LoadBalancer'
Properties:
Name: !Join
- '-'
- - !Ref appname
- 'ALB2'
- !Ref env
Scheme: !FindInMap [ envMap, !Ref env, inorex ]
SecurityGroups: !Split
- ','
- !Join
- ','
- - !Ref SG
- !FindInMap [ envMap, !Ref env, securityGroups ]
Subnets: !FindInMap [ envMap, !Ref env, exsubnets ]
Type: application
IpAddressType: ipv4`
In order to add an additional security group to the list of string values provided by Fn::FindInMap function we need to construct a new list of string values using the return value of Fn::FindInMap and add the additional security group using the Fn::Sub function.
Parameters:
env:
Default: qa
Type: String
Mappings:
envMap:
qa:
securityGroups: 'sg-xxxxxxxx,sg-xxxxxxxx'
sub:
subnets: 'subnet-xxxxxxxx,subnet-xxxxxxxx'
Resources:
LoadBalancer:
Type: 'AWS::ElasticLoadBalancingV2::LoadBalancer'
Properties:
Name: !Join
- '-'
- - OR
- ALB
- !Ref env
Scheme: internal
SecurityGroups: !Split
- ','
- !Sub
- 'sg-xxxxxxx,${mappedGroup}'
- mappedGroup: !FindInMap
- envMap
- !Ref env
- securityGroups
Subnets: !Split
- ','
- !FindInMap
- envMap
- sub
- subnets
Type: application
IpAddressType: ipv4
``
I have to use AWS lambda in various stack of my application, thus I have created a generic cloud-formation template to create a lambda function. This template can be included in another cloud-formation template for further use as a nested stack.
# Basics
AWSTemplateFormatVersion: '2010-09-09'
Description: AWS CloudFormation Template to create a lambda function for java 8 or nodejs
# Parameters
Parameters:
FunctionName:
Type: String
Description: Funciton Name
HandlerName:
Type: String
Description: Handler Name
FunctionCodeS3Bucket:
Type: String
Description: Name of s3 bucket where the function code is present
Default: my-deployment-bucket
FunctionCodeS3Key:
Type: String
Description: Function code present in s3 bucket
MemorySize:
Type: Number
Description: Memory size between 128 MB - 1536 MB and multiple of 64
MinValue: '128'
MaxValue: '1536'
Default: '128'
RoleARN:
Type: String
Description: Role ARN for this function
Runtime:
Type: String
Description: Runtime Environment name e.g nodejs, java8
AllowedPattern: ^(nodejs6.10|nodejs4.3|java8)$
ConstraintDescription: must be a valid environment (nodejs6.10|nodejs4.3|java8) name.
Timeout:
Type: Number
Description: Timeout in seconds
Default: '3'
Env1:
Type: String
Description: Environment Variable with format Key|value
Default: ''
Env2:
Type: String
Description: Environment Variable with format Key|value
Default: ''
Env3:
Type: String
Description: Environment Variable with format Key|value
Default: ''
Env4:
Type: String
Description: Environment Variable with format Key|value
Default: ''
# Conditions
Conditions:
Env1Exist: !Not [ !Equals [!Ref Env1, '']]
Env2Exist: !Not [ !Equals [!Ref Env2, '']]
Env3Exist: !Not [ !Equals [!Ref Env3, '']]
Env4Exist: !Not [ !Equals [!Ref Env4, '']]
# Resources
Resources:
LambdaFunction:
Type: AWS::Lambda::Function
Properties:
Code:
S3Bucket: !Ref 'FunctionCodeS3Bucket'
S3Key: !Ref 'FunctionCodeS3Key'
Description: !Sub 'Lambda function for: ${FunctionName}'
Environment:
Variables:
'Fn::If':
- Env1Exist
-
- !Select [0, !Split ["|", !Ref Env1]]: !Select [1, !Split ["|", !Ref Env1]]
- 'Fn::If':
- Env2Exist
- !Select [0, !Split ["|", !Ref Env2]]: !Select [1, !Split ["|", !Ref Env2]]
- !Ref "AWS::NoValue"
- 'Fn::If':
- Env3Exist
- !Select [0, !Split ["|", !Ref Env3]]: !Select [1, !Split ["|", !Ref Env3]]
- !Ref "AWS::NoValue"
- 'Fn::If':
- Env4Exist
- !Select [0, !Split ["|", !Ref Env4]]: !Select [1, !Split ["|", !Ref Env4]]
- !Ref "AWS::NoValue"
- !Ref "AWS::NoValue"
FunctionName: !Ref 'FunctionName'
Handler: !Ref 'HandlerName'
MemorySize: !Ref 'MemorySize'
Role: !Ref 'RoleARN'
Runtime: !Ref 'Runtime'
Timeout: !Ref 'Timeout'
Outputs:
LambdaFunctionARN:
Value: !GetAtt 'LambdaFunction.Arn'
I want to inject the environment variables to the the function and that will be passed from parent stack as below:
# Resouces
Resources:
# Lambda for search Function
ChildStackLambdaFunction:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: <<REF_TO_ABOVE_LAMBDA_STACK.yml>>
Parameters:
FunctionName: test
HandlerName: 'index.handler'
FunctionCodeS3Bucket: <<BUCKET_NAME>>
FunctionCodeS3Key: <<FUNCTION_DEPLOYMENT_NAME>>
MemorySize: '256'
RoleARN: <<ROLE_ARN>>
Runtime: nodejs6.10
Timeout: '60'
Env1: !Sub 'AWS_REGION|${AWS::Region}'
When I deploy this stack, I am getting below error. Can anybody help me to resolve this one?
Template format error: [/Resources/LambdaFunction/Type/Environment/Variables/Fn::If/1/0] map keys must be strings; received a map instead
Passing key-value parameter is referred from here
So, I tried so many ways to achieve this, but we can not pass the dynamic key-value pair to nested lambda stack from the parent stack. I had a confirmation from the AWS support that this is not possible as this moment.
They suggested a another way which I liked and implemented and its mentioned as below:
Pass the key: value pair as a JSON string and parse it appropriately in the lambda function.
Environment:
Variables:
Env1: '{"REGION": "REGION_VALUE", "ENDPOINT": "http://SOME_ENDPOINT"}'
This suggestion has a little overhead on programming to parse the JSON string, but at this moment I will recommend this as solution for above problem.
I achieved this with the PyPlate macro.
Take environment variables list in a commalimited
Parameters:
EnvVars:
Type: CommaDelimitedList
Description: Comma separated list of Env vars key=value pairs (key1=value1,key2=value2)
and use it in the Lambda Resource:
Environment:
Variables: |
#!PyPlate
output = dict()
for envVar in params['EnvVars']:
key, value = envVar.split('=')
output.update({key: value})
This is the right way to use global variables.
Globals:
Function:
Timeout: 60
Runtime: nodejs10.x
Environment:
Variables:
STAGE: !Ref Stage
DatabaseName: !Ref DatabaseName
DatabaseUsername: !Ref DatabaseUsername
DatabasePassword: !Ref DatabasePassword
DatabaseHostname: !Ref DatabaseHostname
AuthyAPIKey: !Ref AuthyApiKey
I am new to AWS Cloudformation world. I am trying to create an elasticbeanstalk configuration template. For it's one of the setting, I need to use two security groups. So, I have given it as following
MyConfigurationTemplate:
Type: AWS::ElasticBeanstalk::ConfigurationTemplate
Properties:
Properties:
ApplicationName: MyApplication
Description: A default Application
SolutionStackName: SolutionStack
OptionSettings:
....
....
- Namespace: aws:autoscaling:launchconfiguration
OptionName: SecurityGroups
Value:
!If
- ConditionIsTrue
- [!Ref FirstGroup, !ImportValue SecondGroup]
- !Ref FirstGroup
....
....
I read from AWS docs here, that SecurityGroups is a list and we can provide comma seprated list. But it is not working for me. AWS throws following error
Value of property Value must be of type String
I tried giving value of security groups in following ways but none of them worked.
1) "!Ref FirstGroup, !ImportValue SecondGroup"
2) !Ref FirstGroup, !ImportValue SecondGroup
Any idea how this list of security groups should be provided?
I got it myself with some trial and error. As it accepts comma separated list. We need to use !join as follows.
MyConfigurationTemplate:
Type: AWS::ElasticBeanstalk::ConfigurationTemplate
Properties:
ApplicationName: MyApplication
Description: A default Application
SolutionStackName: SolutionStack
OptionSettings:
....
....
- Namespace: aws:autoscaling:launchconfiguration
OptionName: SecurityGroups
Value:
!If
- ConditionIsTrue
- !Join [',', [!Ref FirstGroup, !ImportValue SecondGroup]]
- !Ref FirstGroup
....
....