How do I get public IP of EC2 instance and write to a text file in UserData. Tried the following, but as expected it wrote the text literally, rather than resolving it.
UserData:
Fn::Base64: !Sub |
#!/bin/bash -xe
echo "Public IP: " !GetAtt Bastion.PublicIp > /home/ec2-user/readme.txt
Thanks in advance! (PS : It has to be yaml)
Related
I have a commadelimted list and template snippet like:
AWSTemplateFormatVersion: 2010-09-09
Parameters:
IPWhitelist:
Description: Comma-delimited list of CIDR blocks.
Type: CommaDelimitedList
Default: '1.1.1.1/32, 2.2.2.2/32, 3.3.3.3/32'
Resources:
EC2Instance:
Type: 'AWS::EC2::Instance'
CreationPolicy:
ResourceSignal:
Timeout: PT5M
Metadata:
'AWS::CloudFormation::Init':
configSets:
foobar_setup:
- configure_foo
configure_foo:
commands:
01_config:
command: !Sub |
IFS=', ' read -r -a array <<< "$(echo ${IPWhitelist} | sed -e 's/[][]//g')"
for IP in "${!array[#]}";do echo $IP >> /foo/bar/allowed_ips.txt;done
I'd like to run the following command in the Init type commands key:
IFS=', ' read -r -a array <<< "$(echo ${IPWhitelist} | sed -e 's/[][]//g')"
for IP in "${array[#]}";do echo $IP >> /etc/squid/allowed_ips.txt;done
so for the array as the doc says
To write a dollar sign and curly braces (${}) literally, add an
exclamation point (!) after the open curly brace, such as ${!Literal}.
AWS CloudFormation resolves this text as ${Literal}.
What about the first line? how to substitute cloudformation parameter inside bash command substitution $()?
Error message:
Template contains errors.: Template error: variable IPWhitelist in
Fn::Sub expression does not resolve to a string
I'm writing the cloudformation template that includes ec2 instance. In userdata block, I want to create a file with some content. In the file, I'm initializing local variable MY_MESSAGE, but next, after the template is deployed this variable is not shown in the file.
original temlate:
EC2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-03368e982f317ae48
InstanceType: t2.micro
KeyName: ec2
UserData:
!Base64 |
#!/bin/bash
cat <<EOF > /etc/aws-kinesis/start.sh
#!/bin/sh
MY_MESSAGE="Hello World"
echo $MY_MESSAGE
output file in ec2 instance:
#!/bin/sh
MY_MESSAGE="Hello World"
echo
As you can see variable MY_MESSAGE does not exist in echo block.
You can put EOF in quotes: "EOF":
UserData:
!Base64 |
#!/bin/bash
cat <<"EOF" > /etc/aws-kinesis/start.sh
#!/bin/sh
MY_MESSAGE="Hello World"
echo $MY_MESSAGE
EOF
I created two Amazon EC2 instances in AWS CloudFormation using a YAML template. I want to take private IP address of one EC2 instance to the other EC2 instance which has a public IP address. As per AWS documentation we can do that using !GetAtt JMeterServer1Instance.PrivateIp
I want to know under which section of the public EC2 instance I should add that in the template. (Please consider this is a YAML template.)
How do I check that we have received it?
It appears that your requirement is:
Create two instances in a CloudFormation template
In the User Data for Instance-A, refer to Instance-B
This is quite simple. First, define that Instance-B DependsOn Instance-A to ensure the creation of Instance-A before Instance-B.
Then, in the User Data for Instance-B, refer to Instance-A:
UserData:
"Fn::Base64":
!Sub |
#!/bin/bash
echo "${InstanceA.PrivateIp}" >foo
A 'better' method would be to use DNS names with a Hosted Zone for VPC in Route 53. This would create a DNS zone for the VPC, then define a DNS name that can be resolved locally. Link it to Instance-B and then Instance-A could refer to Instance-B by DNS name rather than IP address. This allows the DNS name to point to a different instance in future if desired, and creates less dependencies between Instance-A and Instance-B. (But, admittedly, more setup.)
As per the AWS document
Fn::GetAtt
will do the trick here.
My case is:
EC2Instance001 needs to be created 1st
EC2Instance002 needs to use IP of EC2Instance001.
At EC2Instance002 instance is created with two specific settings:
"DependsOn": [ "EC2Instance001"] as I want EC2Instance001 to be created first.
Under Userdata (or metadata) use { "Fn::GetAtt" : [ "EC2Instance001", "PrivateIp" ] } for getting IP of 1st Instance (EC2Instance001)
Here is how I achieved it (EC2Instance002):
---
EC2Instance002:
Type: AWS::EC2::Instance
Metadata:
AWS::CloudFormation::Init:
configSets:
InstallAndRun:
- Install
- Configure
Install:
packages:
yum:
git: []
files:
"/tmp/bootstrap.sh":
content:
Fn::Join:
- ''
- - "#!/bin/bash\n"
- 'set -x
'
- 'echo "============================"
'
- 'sudo hostname >> /tmp/EC2Instance.txt
'
- MASTERIP=
- Fn::GetAtt:
- EC2Instance001
- PrivateIp
- "\n"
- "echo $MASTERIP > masterIP.txt \n"
mode: '755'
owner: ec2-user
group: ec2-user
Configure:
commands:
runBootstrapScript:
command: "./bootstrap.sh"
cwd: "/tmp"
DependsOn:
- EC2Instance001
Properties:
InstanceType:
Ref: InstanceType
SecurityGroups:
- Ref: InstanceSecurityGroup
KeyName:
Ref: KeyName
UserData:
Fn::Base64:
Fn::Join:
- ''
- - "#!/bin/bash -xe\n"
- 'yum install -y aws-cfn-bootstrap
'
- "# Install the files and packages from the metadata\n"
- "/opt/aws/bin/cfn-init -v"
- " --stack "
- Ref: AWS::StackName
- " --resource EC2Instance002 "
- " --configsets InstallAndRun "
- " --region "
- Ref: AWS::Region
- "\n"
ImageId:
Fn::FindInMap:
- AWSRegionArch2AMI
- Ref: AWS::Region
- Fn::FindInMap:
- AWSInstanceType2Arch
- Ref: InstanceType
- Arch
You can see that under Metadata I am capturing IP of EC2Instance001 instance in variable $MASTERIP.
NOTE: Same line in JSON will be written as:
"MASTERIP=",{ "Fn::GetAtt" : [ "EC2Instance001", "PrivateIp" ] }, "\n",
It depends on what you'd like to with the private IP on the other machine.
If you'd like to use it in a script on the other VM, pass it down in the user data script like in this example: UserData script with Resource Attribute CloudFormation
The example on the link is showing the attribute of a NetworkInterface instead of an instance attribute, but it's the same with !GetAtt JMeterServer1Instance.PrivateIp
I have UserData with this syntax:
UserData:
Fn::Base64: !Sub |
<script>
.........
cfn-signal.exe ${MyWaitHandle}
</script>
The problem is that ${MyWaitHandle} needs to be encoded with Base64, and I don't know how to do that. I tried with ${Base64:MyWaitHandle} but I got a validation error.
I'm migrating from JSON to YAML, and the original JSON had "cfn-signal.exe ", {"Fn::Base64" : {"Ref" : "MyWaitHandle"}}.
How do I recreate that inside the !Sub | syntax?
Ok so reading the documentation on the The intrinsic function Fn::Sub. I can use a literal block to specify the user data script.
UserData:
"Fn::Base64":
!Sub |
#!/bin/bash -xe
yum update -y aws-cfn-bootstrap
/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource LaunchConfig --configsets wordpress_install --region ${AWS::Region}
/opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource WebServerGroup --region ${AWS::Region}
and I can uses a mapping to substitute the ${Domain} variable with the resulting value from the Ref function.
Name: !Sub
- www.${Domain}
- { Domain: !Ref RootDomainName }
But what if I need to use a mapping substitute inside a literal block? Like for example:
"Fn::Base64": !Sub |
<powershell>
Write-host "My Domain is www.${Domain},{ Domain: !Ref RootDomainName }"
</powershell>
This example does not work, and I haven't been unable to find a method that does. Any ideas? The first example makes userdata scripts much easier to write and looks cleaner, but without being able to to use !Ref or !Findinmap it's usefulness is reduced dramatically.
Anyone got any ideas?
Since I arrived to this page through Google, and then found the solution through a different wording here(literally):
How to use !FindInMap in !Sub | userdata section
I'll just add it to save some frustrated searching to others.
Essentially, you have to write your example using the 2nd syntax but a bit more verbosely:
Fn::Base64:
Fn::Sub:
- |+
<powershell>
Write-host "My Domain is www.${Domain}"
</powershell>
- Domain:
Fn::Ref: RootDomainName
You may be able to shorten it a bit, but as the original poster said, mind your commas, quoting and usage of short forms.
P.S.: If the first solution has already served your purpose, you should mark it thus.
In that case you would write it simply as ${RootDomainName}. Local resouces in the same stack can just be mapped by using their resource name.