Conditionally define Elastic Beanstalk environment variable using CloudFormation
B

3

6

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 ""

Brina answered 1/3, 2019 at 21:56 Comment(1)
!Equals is not allowed within a Fn::If block ... so what you claim above will not work. Yeah, I know your issue is how to avoid setting the variable (even to null or empty string) if the condition is not met, but just pointing this out so other people don't get confused by the obvious error in the code have sharedKilliecrankie
A
5

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.

Atomism answered 1/3, 2019 at 22:19 Comment(2)
Yeah, this is slightly better than my backup plan to have 2 separate templates. Thanks!Brina
Not an option for resources that occupy many lines of code. OK suggestion, I guess, for shorter lines of code or if one is really desperateKilliecrankie
I
3

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.

India answered 1/1, 2021 at 7:6 Comment(2)
Good suggestion, but setting timeout has it's own considerations too. I guess the logic is to find a safe parameter, even if it's to re-set the default, and use it as a fallback. That can save many lines of code, especially for verbose resources like elasticbeanstalkKilliecrankie
Timeout is an example that I used in my scenario. You can use any parameter that you are already using in your EB environment. And yes, we can even re-set the default just to achieve this.India
D
1

Yet another option is to use ConfigurationTemplate "inheritance", using the SourceConfiguration property.

Basically, you create a parent configuration template with all desired properties except the environment property, and you conditionally create a child template that specifies the parent as its SourceConfiguration. The child template just adds the desired environment property. Then you use the same condition to assign the appropriate template to the environment.

Here it is applied to the OP's example :

Parameters:
  var1:
    Type: String

Conditions:
  Var1NotEmpty: !Not [!Equals [ !Ref var1, "" ]]

Resources:
  ElasticBeanstalkEnvironment:
    Type: 'AWS::ElasticBeanstalk::Environment'
    Properties:
      ApplicationName: !Ref ApplicationName
      TemplateName: !If [Var1NotEmpty, !Ref TemplateWithVar, !Ref TemplateWithoutVar]

  TemplateWithoutVar:
    Type: AWS::ElasticBeanstalk::ConfigurationTemplate
    Properties:
      ApplicationName: !Ref ApplicationName
      Description: "specifies all configuration settings except the optional ENV_VAR_1"
      SolutionStackName: "64bit Amazon Linux 2023 v4.1.0 running Python 3.11"
      OptionSettings:
        - Namespace: "aws:autoscaling:launchconfiguration"
          OptionName: "IamInstanceProfile"
          Value: "aws-elasticbeanstalk-ec2-role"

  TemplateWithVar:
    Type: AWS::ElasticBeanstalk::ConfigurationTemplate
    Condition: Var1NotEmpty
    Properties:
      ApplicationName: !Ref ApplicationName
      Description: "only adds the optional ENV_VAR_1"
      SourceConfiguration:
        ApplicationName: !Ref ApplicationName
        TemplateName: !Ref TemplateWithoutVar
      OptionSettings:
        - Namespace: 'aws:elasticbeanstalk:application:environment'
          OptionName: ENV_VAR_1
          Value: !Ref var1

Clearly this becomes too cumbersome if you've got multiple optional variables.

An interesting use-case would be the implementation of an optional SSH key, as in

...
        - Namespace: "aws:autoscaling:launchconfiguration"
          OptionName: "EC2KeyName"
          Value: !Ref KeyName

where KeyName can be an empty String.

Diatropism answered 26/6 at 13:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.