Hello!
In CloudFormation, it’s common to construct strings with the !Join
function. Like this example from AWS’s cfn-init
docs:
UserData: !Base64 'Fn::Join': - '' - - | #!/bin/bash -xe - | # Install the files and packages from the metadata - '/opt/aws/bin/cfn-init -v ' - ' --stack ' - !Ref 'AWS::StackName' - ' --resource WebServerInstance ' - ' --configsets InstallAndRun ' - ' --region ' - !Ref 'AWS::Region' - |+
Here’s the script this renders into:
#!/bin/bash -xe # Install the files and packages from the metadata /opt/aws/bin/cfn-init -v --stack test --resource WebServerInstance --configsets InstallAndRun --region us-west-2
To me, both are messy and hard to read. It abuses multiline string declarations (|
) to create single line breaks. Spaces are all over the place. There are YAML -
and '
characters everywhere.
I use the !Sub
function with one multi-line string instead:
UserData: Fn::Base64: !Sub | #!/bin/bash -xe # Install the files and packages from the metadata /opt/aws/bin/cfn-init -v \ --stack ${AWS::StackName} \ --resource WebServerInstance \ --configsets InstallAndRun \ --region ${AWS::Region}
Fewer lines, no YAML syntax scattered around the script. It reads like a normal shell script except we can use ${}
wherever we’d have used a !Ref
. It renders like this:
#!/bin/bash -xe # Install the files and packages from the metadata /opt/aws/bin/cfn-init -v \ --stack test \ --resource WebServerInstance \ --configsets InstallAndRun \ --region us-west-2
I think both are much easier to read.
Some details:
- I used
Fn::Base64
instead of!Base64
because you can’t mix the long and short form in this case. - If your string needs values from other functions, like
!GetAtt
or!ImportValue
, check out this. - Every new line in the sub version is a new line in the rendered script. Like any multiline shell command it has to break lines between the arguments with
\
. The join version renders thecfn-init
command into one long line with a ton of spaces between the arguments, and a side effect is they don’t need the multiline command syntax. - The
${thing}
syntax of!Sub
is also a type shell variable expansion. Make sure you only use it for CloudFormation references. No problem for me because I only use CloudFormation to render super simple scripts that basically just callcfn-init
. Shell’s$thing
syntax is all I need. If your script is complex enough that this isn’t enough, I recommend reconsidering your approach. It’s usually an anti-pattern to use CloudFormation for those cases.
I almost always use !Sub
instead of !Join
. It lets you write strings like the strings they are, rather than polluting them with a bunch of YAML syntax.
Happy automating!
Adam
Need more than just this article? I’m available to consult.
You might also want to check out these related articles: