AWS CloudFormation UserData EC2 Environment Variable - amazon-web-services

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.

Related

Reference ID of resource in Userdata Cloudformation

I'm writing a cloudformation template in YAML format.
Now i'm stuck with appending the id of the ebs volume to ec2 user data.
Type: AWS::EC2::Volume
Properties:
Size: 50
AvailabilityZone: ap-southeast-1b
Tags:
- Key: Name
Value: Logstash Volume
LogStashMountPoint:
Type: AWS::EC2::VolumeAttachment
Properties:
InstanceId:
Ref: LogstashInstance
VolumeId:
Ref: LogstashVolume
Device: "/dev/xvdf"
LogstashInstance:
Type: AWS::EC2::Instance
Properties:
IamInstanceProfile:
Ref: LogstashInstanceProfile
InstanceType: t2.micro
KeyName: chuongtest
ImageId: ami-0cd31be676780afa7
UserData:
Fn::Base64:
Fn::Sub:
- |
#!/bin/bash -xe
echo ${LogstashVolume} >> /home/ec2-user/ebsid.txt
{LogstashVolume: Ref: LogstashVolume}
touch /home/ec2-user/ebscomplete.txt
curl "http://169.254.169.254/latest/meta-data/instance-id" >> /home/ec2-user/ec2id.txt
touch /home/ec2-user/ec2complete.txt
touch /home/ec2-user/complete.txt
- LogstashVolume: !Ref LogstashVolume
SecurityGroupIds:
- Ref: LogstashSecurityGroup
SubnetId: subnet-0d0e0989f57b96389
Tags:
- Key: Name
Value: Logstash Instance
UserData script with Resource Attribute CloudFormation
I'm following this link but it still doesn't work
When the new instance is launched. It has nothing in /home/ec2-user.
I looked everywhere and this is my final but it didn't work
Can anyone help me with it?
There is at least one syntax error in your UserData:
echo ${LogstashVolume}) >> /home/ec2-user/ebsid.txt
should be
echo ${LogstashVolume} >> /home/ec2-user/ebsid.txt
For further debugging of your UserData, login to the instance and check /var/log/could-init-output.log file.
p.s.
The following is also incorrect:
{LogstashVolume: Ref: LogstashVolume}
Its not a valid bash command.

Terraform cloudformation_stack unkown variable onto UserData

I have a terraform the create a stack on AWS from an yaml file.
My resource "aws_cloudformation_stack" "gitlab-runner" has the following parameters:
Token = "GAdt_YVHgcp5QM_Nms65"
IAMRoleName = "${module.gitlab-iam.iam_role_name}"
My yaml file has the following statements:
Parameters:
GitLabRunnerToken:
Description: >-
Registration token for GitLab Runner. Registration token must contain
exactly 20 alphanumeric characters
AllowedPattern: '^[-_a-zA-Z0-9]*$'
Type: String
MinLength: '20'
MaxLength: '20'
NoEcho: true
Resources:
...
LaunchConfiguration:
Type: AWS::AutoScaling::LaunchConfiguration
Properties:
ImageId: !FindInMap [AWSRegionToAMI, !Ref 'AWS::Region', AMIID]
SecurityGroups:
- !Ref SecurityGroup
InstanceType: !Ref InstanceType
IamInstanceProfile: !Ref GitlabRunnerInstanceProfile
KeyName: !Ref KeyName
BlockDeviceMappings:
- DeviceName: /dev/xvdb
Ebs:
VolumeSize: !Ref 'VolumeSize'
VolumeType: !Ref 'VolumeType'
DeleteOnTermination: !Ref 'DeleteOnTermination'
UserData:
Fn::Base64: !Sub |
#!/bin/bash -xe
docker run --rm -t -i -v /srv/gitlab-runner/config:/etc/gitlab-runner gitlab/gitlab-runner register \
--non-interactive \
--executor "docker" \
--docker-image alpine:3 \
--url "https://gitlab.affinitas.de" \
--registration-token ${GitLabRunnerToken} \
--description "docker-runner" \
--tag-list "docker,aws" \
--run-untagged \
--locked="false"
I am not able to get the value of the parameter GitLabRunnerToken and inject inside UserData: Base64: !Sub |
I got error below:
<template_file>:160,34-51: Unknown variable; There is no variable named "GitLabRunnerToken".
I tried fetch the value using:
1. ${GitLabRunnerToken}
2. Ref: "GitLabRunnerToken"
3. !Ref: "GitLabRunnerToken"
4. !ImportValue "GitLabRunnerToken"
But I am still not able to get and pass the valeu for my UserData, LaunchConfiguration.
Any clue on it?
Thank you.
It's because you use ${variable} format. You pass variables to your cloud-init/userdata scripts this way. Terraform foolishly tires to replace anything "${something}" with values in the vars section of template_file. Lose {} and you'll be fine.

AWS Cloudformation: Conditionally create properties of resources

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

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

Reference Parameter Value in UserData in AWS Cloudformation

I have this under parameter section ,
Parameters:
PlatformSelect:
Description: Cockpit platform Select.
Type: String
Default: qa-1
AllowedValues: [qa-1, qa-2, staging, production]
I need to reference this value in my UserData. I’m using Mappings in between.
Mappings:
bootstrap:
ubuntu:
print: echo ${PlatformSelect} >>test.txt
Resources:
EC2Instance:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref ‘InstanceType’
KeyName: !Ref ‘KeyName’
Tags:
- Key: Name
Value: Test
UserData:
Fn::Base64:
Fn::Join:
- ‘’
- - |
#!/bin/bash
- Fn::FindInMap:
- bootstrap
- ubuntu
- print
- |2+
This is not working. Not sure the way I refer it is wrong in first place!!
Should I use something before it like, ‘${AWS::Parameters:PlatformSelect}’ ?
Is there a reason why you are using Mapping in between?
You could easily use !Sub instead
Resources:
EC2Instance:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref InstanceType
KeyName: !Ref KeyName
Tags:
- Key: Name
Value: Test
UserData:
Fn::Base64:
!Sub |
#!/bin/bash
${PlatformSelect}
What about a combination of Fn::Join and Ref
UserData:
Fn::Base64:
Fn::Join:
- ''
- - '#!/bin/bash\n'
- 'print: echo'
- !Ref 'PlatformSelect'
- '>>test.txt\n'