AWS Cloudformation: Conditionally create properties of resources - amazon-web-services

I know that it is possible via the use of Conditions to conditionally (what else?) create resources.
I am trying to find a way though to conditionally create properties of resources;
in my case I am creating several EC2 instances in a subnet with default public ip assignment = false.
Sometimes though for debugging purposes I want my instances to get public IPs.
Now I have to comment in/out the SG/Subnet and the NetworkInterfaces properties below (those do not go together)
myEC2:
Type: AWS::EC2::Instance
Metadata:
Comment: My EC2 Instance
AWS::CloudFormation::Init:
config:
commands:
01_provision:
command:
!Sub |
sed -i "s/somestring/${somevar}/" /some/path/
CreationPolicy:
ResourceSignal:
Timeout: PT4M
Properties:
ImageId: !FindInMap [ MyAamiMap, 'myami', amiid ]
InstanceType: "t2.2xlarge"
# SubnetId: !Ref SBNDemo1
# SecurityGroupIds: [!Ref SGInternalDemo]
NetworkInterfaces:
- AssociatePublicIpAddress: "true"
DeviceIndex: "0"
GroupSet:
- Ref: "SGInternalDemo"
SubnetId:
Ref: "SBNDemo1"
UserData:
"Fn::Base64":
!Sub |
#!/bin/bash -xe
# Start cfn-init
/usr/local/bin/cfn-init -s ${AWS::StackId} -r myEC2 --region ${AWS::Region} || echo 'Failed to run cfn-init'
# All done so signal success
/usr/local/bin/cfn-signal -e $? --stack ${AWS::StackId} --resource myEC2 --region ${AWS::Region}
Any suggestions?

This might be a little late, but I recently had a same question.
From AWS docs, you can use Fn::If to set properties accordingly.
The template will look like:
Properties:
ImageId: !FindInMap [ MyAamiMap, 'myami', amiid ]
InstanceType: "t2.2xlarge"
# SubnetId: !Ref SBNDemo1
# SecurityGroupIds: [!Ref SGInternalDemo]
NetworkInterfaces:
!If
- YourCondition
-
AssociatePublicIpAddress: "true"
DeviceIndex: "0"
GroupSet:
- Ref: "SGInternalDemo"
SubnetId:
Ref: "SBNDemo1"
- !Ref "AWS::NoValue"
AWS::NoValue means there will be no NetworkInterfaces properties set.

Perhaps I am misunderstanding but this sounds like a parameter use case rather than a condition use case. I say that because you do not say under what conditions you would like a public ip. Just "sometimes for debugging purposes" How would the template know that you are debugging? You have to tell it with a parameter.
check out the docs https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html
So you could have a public ip parameter and a subnet id parameter and pass in what you like at stack creation.
One way that conditions could be useful is to create a debug parameter that would toggle public/private ip and subnet. Is this what you were thinking of?
To use conditions on properties use the IF function
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-conditions.html
I suggest setting your public subnet to provide a public ip on launch, and of course ensuring your private subnet does not do that. Then just pass the subnet in as a parameter.
https://docs.aws.amazon.com/vpc/latest/userguide/vpc-ip-addressing.html#subnet-public-ip

Related

AWS Cloudformation Default keypair for ec2

I have a cloudformation template to create an ec2-instance. That template also starts an httpd along with some content that is served.
I'm using the Parameter section to allow a key to be specified or selected - see snippet below:
Parameters:
paramKeyPair:
Description: KeyPairName
Type: AWS::EC2::KeyPair::KeyName
I'm calling the ec2-instance through the AWS CLI like this :
aws cloudformation create-stack --stack-name stack-ec2instance --template-body file://demo-ec2instance --parameters ParameterKey=paramKeyPair,ParameterValue=peterKeyPair
So the instance can be created and the keypair can be passed through as an argument - BUT - frankly I don't actually care that much if the instance can be access. It's just a web server that can be spun up or down. SSH access is nice but no big deal.
In fact, if I removed the keypair Parameter from the cloudformation template - and removed the associated reference in the AWS CLI call - Cloudformation will happily spin up the instance without a keypair. Great !
What I would really like is for cloudformation to deal with the keypair being present or not.
I thought the best way to do this would be to update the code so that the parameter has a default value of "None" (for example) and then the ec2-instance could be run from the AWS CLI and if the keypair parameter is not specified then AWS would know not to bother with the keypair at all.
The problem is that by specifying the Type as AWS::EC2::KeyPair::KeyName, the AWS CLI expects an actual value.
I'm out of ideas - if anyone else has figured this out - I would really appreciate it.
Thankyou
Peter.
If I understand you correctly you want to be able to keep the parameter in your Cloudformation template, but only "allocate" a key pair to an instance if you specify a value, otherwise don't allocate a key pair to the ec2 instance resource. You can do this with AWS::NoValue pseudo parameter.
Here is a sample template:
Description: My EC2 instance
Parameters:
SSHKeyName:
Type: String
Conditions:
Has-EC2-Key:
!Not [ !Equals [ !Ref SSHKeyName, '' ] ]
Resources:
Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: <InstanceImageID>
InstanceType: t2.micro
KeyName: !Ref SSHKeyName
KeyName:
Fn::If:
- Has-EC2-Key
- Ref: SSHKeyName
- Ref: AWS::NoValue
<other properties as required
So what this does is the condition checks if a SSHKeyName value is blank, if it's blank then the KeyName property will be ignored, if it isn't blank then it will use the value of SSHKeyName.
Thankyou WarrenG, your solution worked with one small exception which was to change the parameter type from AWS::EC2::KeyPair::KeyName to String.
Without your help I am certain I would have burned many more hours on this.
So in conclusion, the fix was
1: Change the Parameter type to String.
Parameters:
SSHKeyName:
Type: String
2: Add a function that determines if the key is present.
Conditions:
Has-EC2-Key:
!Not [ !Equals [ !Ref SSHKeyName, '' ] ]
Use the function within the resources section.
KeyName:
Fn::If:
- Has-EC2-Key
- Ref: SSHKeyName
- Ref: AWS::NoValue
Within my question I kept the code snippets to a minimum for readability but now I have marked this as solved I'm adding two blocks of code just for documentation and incase this helps anyone else.
One example of calling the template through the AWS CLI.
aws cloudformation create-stack --stack-name stack-ec2instance --template-body file://demo-ec2instance --parameters ParameterKey=paramSubnetId,ParameterValue=$SubnetId ParameterKey=paramKeyPair,ParameterValue=peterKeyPair ParameterKey=paramSecurityGroupIds,ParameterValue=$SecurityGroupId
The template to create a EC2 instance.
AWSTemplateFormatVersion: 2010-09-09
Parameters:
SSHKeyName:
Description: EC2 KeyPair for SSH access.
Type: String
Conditions:
Has-EC2-Key:
!Not [ !Equals [ !Ref SSHKeyName, '' ] ]
Mappings:
RegionMap:
eu-west-1:
AMI: ami-3bfab942
eu-west-2:
AMI: ami-098828924dc89ea4a
Resources:
EC2Instance:
Type: AWS::EC2::Instance
Metadata:
AWS::CloudFormation::Init:
config:
packages:
yum:
httpd: []
php: []
files:
/var/www/html/index.php:
content: !Sub |
<?php print "Hello Peter !"; ?>
services:
sysvinit:
httpd:
enabled: true
ensureRunning: true
Properties:
InstanceType: t2.micro
ImageId:
Fn::FindInMap:
- RegionMap
- !Ref AWS::Region
- AMI
SecurityGroupIds:
- !Ref MySecurityGroup
KeyName:
Fn::If:
- Has-EC2-Key
- Ref: SSHKeyName
- Ref: AWS::NoValue
UserData:
'Fn::Base64':
!Sub |
#!/bin/bash -xe
# Ensure AWS CFN Bootstrap is the latest
yum install -y aws-cfn-bootstrap
# Install the files and packages from the metadata
/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource EC2Instance --region ${AWS::Region}
MySecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Open Ports 22 and 80
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: '22'
ToPort: '22'
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: '80'
ToPort: '80'
CidrIp: 0.0.0.0/0
Outputs:
Website:
Description: The Public DNS for the EC2 Instance
Value: !Sub 'http://${EC2Instance.PublicDnsName}'

Cloudformation stack default parameter SSM Parameter store

I am trying to launch a jupyterlab instance using cloudformation (its something I do a lot and sagemaker does not have a 1y free tier) so the beginning looks like this which does not work. Specifically the password parameter
# AWSTemplateFormatVersion: "2010-09-09"
Description: Creates a Jupyter Lab Instance with an Elastic Load Balancer
Parameters:
KeyName:
Description: >-
Name of an existing EC2 KeyPair to enable SSH access to the instance
Type: AWS::EC2::KeyPair::KeyName
ConstraintDescription: Must be the name of an existing EC2 KeyPair.
Default: eduinstance
VPC:
Description: VPC ID of the VPC in which to deploy this stack.
Type: AWS::EC2::VPC::Id
ConstraintDescription: Must be the name of a valid VPC
Default: vpc-10a7ac6a
Subnets:
Type: List<AWS::EC2::Subnet::Id>
Default: subnet-8cde25d3,subnet-531fda72,subnet-4bbe3006
Description: >-
Subnets for the Elastic Load Balancer.
Please include at least two subnets
Password:
Type: String
NoEcho: false
MinLength: 4
Default: '{{resolve:ssm:JLabPassword:1}}'
Description: Password to set for Jupyter Lab
EBSVolumeSize:
Type: Number
Description: EBS Volume Size (in GiB) for the instance
Default: 8
MinValue: 8
MaxValue: 64000
ConstraintDescription: Please enter a value between 8 GB and 64 TB
EC2InstanceType:
Type: String
Default: t2.micro
AllowedValues:
- t2.micro
- c5.large
- m5.large
Description: Enter t2.micro, c5.large or m5.large. Default is t2.micro.
Conditions:
JupyterPasswordDefault: !Equals
- !Ref Password
- DEFAULT
Resources:
ALB:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
IpAddressType: ipv4
Scheme: internet-facing
SecurityGroups:
- !GetAtt [ALBSG, GroupId]
Subnets: !Ref Subnets
Type: application
ALBListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: forward
TargetGroupArn: !Ref ALBTargetGroup
LoadBalancerArn: !Ref ALB
Port: 80
Protocol: HTTP
ALBTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Port: 8888
Protocol: HTTP
Targets:
- Id: !Ref ComputeInstance
TargetType: instance
VpcId: !Ref VPC
ComputeInstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Roles:
- !Ref ComputeIAMRole
ComputeInstance:
Type: AWS::EC2::Instance
Properties:
InstanceType: t2.micro
SubnetId: !Select [0, !Ref Subnets]
KeyName: !Ref KeyName
ImageId: '{{resolve:ssm:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2:33}}'
SecurityGroupIds:
- !GetAtt [ComputeSG, GroupId]
IamInstanceProfile: !Ref ComputeInstanceProfile
BlockDeviceMappings:
- DeviceName: /dev/xvda
Ebs:
VolumeType: gp2
VolumeSize: !Ref EBSVolumeSize
DeleteOnTermination: true
UserData:
Fn::Base64: !Sub
- |
#!/bin/bash
yum update -y
yum install python3-pip -y
yum install java-1.8.0-openjdk -y
cd /home/ec2-user/
wget https://repo.anaconda.com/archive/Anaconda3-2020.11-Linux-x86_64.sh
sudo -u ec2-user bash Anaconda3-2020.11-Linux-x86_64.sh -b -p /home/ec2-user/anaconda
echo "PATH=/home/ec2-user/anaconda/bin:$PATH" >> /etc/environment
source /etc/environment
jupyter notebook --generate-config
mkdir .jupyter
cp /root/.jupyter/jupyter_notebook_config.py /home/ec2-user/.jupyter/
echo "c = get_config()" >> .jupyter/jupyter_notebook_config.py
echo "c.NotebookApp.ip = '*'" >> .jupyter/jupyter_notebook_config.py
NB_PASSWORD=$(python3 -c "from notebook.auth import passwd; print(passwd('${password}'))")
echo "c.NotebookApp.password = u'$NB_PASSWORD'" >> .jupyter/jupyter_notebook_config.py
rm Anaconda3-2020.11-Linux-x86_64.sh
mkdir Notebooks
chmod 777 -R Notebooks .jupyter
su -c "jupyter lab" -s /bin/sh ec2-user
- password: !Ref Password #!If [JupyterPasswordDefault, '{{resolve:ssm:JupyterLabPassword:1}}', !Ref Password]
ALBSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security Group for JupyterLab ALB. Created Automatically.
SecurityGroupIngress:
- CidrIp: 0.0.0.0/0
Description: Allows HTTP Traffic from anywhere
FromPort: 80
ToPort: 80
IpProtocol: tcp
ComputeSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security Group for JupyterLab EC2 Instance. Created Automatically.
SecurityGroupIngress:
- Description: Allows JupyterLab Server Traffic from ALB.
FromPort: 8888
IpProtocol: tcp
SourceSecurityGroupId: !GetAtt [ALBSG, GroupId]
ToPort: 8890
- CidrIp: 0.0.0.0/0
Description: Allows SSH Access from Anywhere
FromPort: 22
ToPort: 22
IpProtocol: tcp
ComputeIAMRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- 'sts:AssumeRole'
Description: Allows EC2 Access to S3. Created Automatically.
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonS3FullAccess
- arn:aws:iam::aws:policy/AmazonSageMakerFullAccess
Outputs:
URL:
Description: URL of the ALB
Value: !Join
- ''
- - 'http://'
- !GetAtt
- ALB
- DNSName
ConnectionString:
Description: Connection String For SSH On EC2
Value: !Join
- ''
- - 'ssh -i "'
- !Ref KeyName
- '.pem" ec2-user#'
- !GetAtt
- ComputeInstance
- PublicDnsName
It however interprets the string literally so I don't actually get my password but the resolve... itself.
Based on the comments and new, updated template by OP, and to expand on #DennisTraub answer.
SSM parameters resolve in almost all cases in the template, with the exception of UserData (btw, Init will also not work). This means, that dynamic reference will not resolve when used in the context of UserData. This is due to security issues.
UserData can be read in plain text by anyone who can view basic attributes of the instance. This means, that your JLabPassword would be in plain text available in UserData for everyone to see, if such resolution would be possible.
To rectify the issue, the SSM parameters should be used in UserData as follows:
Attach IAM permission ssm:GetParameter to the instance role/profile which allows instance to access the SSM Parameter Store.
Instead on {{resolve:ssm:JLabPassword:1}} in your Parameter, you can just pass JLabPassword so that the name of the SSM paramtter gets passed into the UserData, not the actual value of it.
In the UserData, please use AWS CLI get-parameter to get the actual value of your JLabPassword.
The above ensures that the value of JLabPassword is kept private and not visible in plain text in UserData.
Your passwort parameter's default value is missing the service name (ssm) as well as single quotes.
// What you have:
Password:
Default: {{resolve:JupyterPassword:1}}
...
// What it should be:
Password:
Default: '{{resolve:ssm:JupyterPassword:1}}'
...
Update: You've fixed the code in your question. Did my answer and the comments below solve your question? If not, I'm not sure what else you need.

AWS CloudFormation UserData EC2 Environment Variable

I'm working on an infrastructure with CloudFormation.
That is my own infrastructure code shown below .
AWSRegionArch2AMI:
us-east-1:
HVM64: ami-0ff8a91507f77f867
HVMG2: ami-0a584ac55a7631c0c
...
..
.
Resources:
EC2Instance:
Type: AWS::EC2::Instance
Properties:
InstanceType:
Ref: InstanceType
SecurityGroups:
- Ref: InstanceSecurityGroup
KeyName:
Ref: KeyName
UserData:
Fn::Base64: !Sub |
#!/bin/bash
export MY_AMI_NAME=${ !GetAtt AWSRegionArch2AMI.us-east-1.HVM64 }
echo $MY_AMI_NAME > $HOME/user_data.txt
I want to set the variable to the user_data file but it is empty, How I can get the environment variable to inside my user data field and use it my own application side How I can do it.
Please help !
Try this:
UserData:
Fn::Base64: !Sub
- |
#!/bin/bash
MY_AMI_NAME=${image_id}
echo $MY_AMI_NAME > $HOME/user_data.txt
- image_id: !GetAtt AWSRegionArch2AMI.us-east-1.HVM64
Explanation:
Use of export in Bash is to make variables available to subshells - "environment variables". You don't need it there.
See the docs for proper use of the !Sub function.
See also this related Stack Overflow answer.

Refactor repeated parameters in cloud formation template?

I'm looking for a way to refactor repeated value imports in my Cloud Formation template.
I have the following template which configures a simple app:
Parameters:
KeyName:
Description: Name of an existing EC2 KeyPair to enable SSH access into the server
Type: AWS::EC2::KeyPair::KeyName
S3StackName:
Description: Name of S3 Stack
Type: String
Resources:
EC2Instance:
Type: AWS::EC2::Instance
Metadata:
AWS::CloudFormation::Init:
config:
packages:
yum:
httpd: []
php: []
files:
/var/www/html/index.html:
source:
Fn::Sub:
- https://s3.amazonaws.com/${bucketName}/index.html
- bucketName:
Fn::ImportValue:
!Sub "${S3StackName}-s3Bucket"
/var/www/html/styles.css:
source:
Fn::Sub:
- https://s3.amazonaws.com/${bucketName}/styles.css
- bucketName:
Fn::ImportValue:
!Sub "${S3StackName}-s3Bucket"
/var/www/html/script.js:
source:
Fn::Sub:
- https://s3.amazonaws.com/${bucketName}/script.js
- bucketName:
Fn::ImportValue:
!Sub "${S3StackName}-s3Bucket"
services:
sysvinit:
httpd:
enabled: true
ensureRunning: true
AWS::CloudFormation::Authentication:
S3AccessCreds:
type: S3
roleName: !Ref EC2InstanceRole
buckets:
-
Fn::ImportValue:
!Sub "${S3StackName}-s3Bucket"
Properties:
IamInstanceProfile: !Ref EC2InstanceProfile
InstanceType: t2.micro
ImageId: ami-1853ac65
SecurityGroupIds:
- !Ref MySecurityGroup
KeyName: !Ref KeyName
UserData:
'Fn::Base64':
!Sub |
#!/bin/bash -xe
# Ensure AWS CFN Bootstrap is the latest
yum install -y aws-cfn-bootstrap
# Install the files and packages from the metadata
/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource EC2Instance --region ${AWS::Region}
MySecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Open Ports 22 and 80
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: '22'
ToPort: '22'
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: '80'
ToPort: '80'
CidrIp: 0.0.0.0/0
Outputs:
Website:
Description: The Public DNS for the EC2 Instance
Value: !Sub 'http://${EC2Instance.PublicDnsName}'
You'll notice that there's quite a bit of repetition, particularly importing a value that has been exported from an already existing stack, e.g:
Fn::Sub:
- https://s3.amazonaws.com/${bucketName}/index.html
- bucketName:
Fn::ImportValue:
!Sub "${S3StackName}-s3Bucket"
This pattern is used a grand total of 4 times in the template that I posted above. I want to simplify this some I'm not repeating the same block of YAML over and over again.
My first thought was adding a value to the Metadata section of the template, but that didn't work, as the resources section cannot !Ref from the metadata section.
How can I reduce the amount of repeated YAML in this template?
You should be able to achieve this with a CloudFormation Macro.
This blog post gives a good overview of macros. You can define a macro that calls a simple lambda function and transforms the template, so you can do lots of interesting things with macros. Here are some examples on GitHub.
Another option to investigate is cfndsl, a domain specific language that makes some things like parameters and templates a bit easier.
You can use Parameters:
Example:
Parameters:
FunctionRepeat:
Fn::Sub:
- https://s3.amazonaws.com/${bucketName}/index.html
- bucketName:
Fn::ImportValue:
!Sub "${S3StackName}-s3Bucket"
Then you can reuse this block wherever you like.
Example:
files:
/var/www/html/index.html:
source:
Ref: FunctionRepeat
/var/www/html/styles.css:
source:
Ref: FunctionRepeat
/var/www/html/script.js:
source:
Ref: FunctionRepeat
For more information you can go to:
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/gettingstarted.templatebasics.html
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html

Using Conditions and Parameters in CloudFormation

I am trying to create a condition based on an optional parameter. The option is whether to run some additional installation from my userData script for an EC2 deployment.
The parameters and conditions look like this:
Parameters:
EnvType:
Description: Option to install New Relic Infrastructure.
Default: apm
Type: String
AllowedValues:
- apm
- +infra
Then my EC2 Resource with conditional startup scripts
Resources:
Ec2Instance:
Type: AWS::EC2::Instance
Properties:
InstanceType: t2.micro
ImageId: ami-9c9443e3 #Amazon Linux AMI in Tokyo
KeyName: tokyocloudformation
IamInstanceProfile: 'S3EC2'
SecurityGroupIds:
- !Ref myNewSecurityGroup
UserData:
Condition: apmOnly
Fn::Base64:
|
#!/bin/bash
installstuff
Condition: addInfrastructureAgent
Fn::Base64:
|
#!/bin/bash
installstuff
installsomeotherstuff
The error message I'm getting is:
Template validation error: Template format error: Unresolved dependencies [EnvTyp]. Cannot reference resources in the Conditions block of the template
I get what the error is saying I believe, but it doesn't seem to fit with the examples given by AWS.
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/conditions-section-structure.html .
This AWS example clearly uses a !Ref in the Conditions block.
EnvType:
Description: Environment type.
Default: test
Type: String
AllowedValues:
- prod
- test
ConstraintDescription: must specify prod or test.
Conditions:
CreateProdResources: !Equals [ !Ref EnvType, prod ]
Can someone provide some feedback on how to implement this conditional or why this error message is being thrown for this implementation?
According to the docs, Conditions should be used at the top level of the resource you want to conditionally create.
Putting a Condition inside the Instance UserData section isn't supported.
To use Conditions in your situation, you'd want separate Resources conditionally created based on the Parameter.
Resources:
Ec2InstanceAPMOnly:
Type: AWS::EC2::Instance
Condition: apmOnly
Properties:
InstanceType: t2.micro
ImageId: ami-9c9443e3 #Amazon Linux AMI in Tokyo
KeyName: tokyocloudformation
IamInstanceProfile: 'S3EC2'
SecurityGroupIds:
- !Ref myNewSecurityGroup
UserData:
Fn::Base64:
!Sub |
#!/bin/bash
installstuff
Ec2InstanceWithInfrastructureAgent:
Type: AWS::EC2::Instance
Condition: addInfrastructureAgent
Properties:
InstanceType: t2.micro
ImageId: ami-9c9443e3 #Amazon Linux AMI in Tokyo
KeyName: tokyocloudformation
IamInstanceProfile: 'S3EC2'
SecurityGroupIds:
- !Ref myNewSecurityGroup
UserData:
Fn::Base64:
!Sub |
#!/bin/bash
installstuff
installsomeotherstuff
You can use Conditions in userData as well. i followed this post https://www.singlestoneconsulting.com/blog/cloudformation-mapping-and-conditionals-making-your-templates-more-universal/ and it worked perfactlly