Is there any way to define shortcuts for often-used values derived from CloudFormation template parameters?
For example - I've got a script that creates a Multi-AZ Project stack with ELB name project
and two instances behind the ELB called project-1
and project-2
. I only pass ELBHostName
parameter to the template and later on use it to construct :
"Fn::Join": [
".", [
{ "Fn::Join": [ "", [ { "Ref": "ELBHostName" }, "-1" ] ] },
{ "Ref": "EnvironmentVersioned" },
{ "Ref": "HostedZone" }
]
]
This construction or very similar is repeated many times throughout the template - to create the EC2 host name, Route53 records, etc.
Instead of repeating that over and over again I would like to assign the output of that Fn::Join
to a variable of some sort and only refer to that, just like I can with "Ref":
statement.
Ideally something like:
Var::HostNameFull = "Fn::Join": [ ... ]
...
{ "Name": { "Ref": "Var::HostNameFull" } }
or something similarly simple.
Is that possible with Amazon CloudFormation?
I don't have an answer, but did want to point out that you can save yourself a lot of pain by using
Fn::Sub
in place ofFn::Join
Replaces
No. I tried it, but came up empty. The way that made sense to me was to create a Mappings entry called "CustomVariables" and to have that house all my variables. It works for simple Strings, but you can't use Intrinsics (Refs, Fn::Joins, etc.) inside Mappings.
Works:
Won't work:
That's just an example. You wouldn't put a standalone Ref in a Variable.
I was looking of the same functionality. Using a nested stack as SpoonMeiser suggested came to mind, but then I realised that what I actually needed was custom functions. Luckily CloudFormation allows the use of AWS::CloudFormation::CustomResource that, with a bit of work, allows one to do just that. This feels like overkill for just variables (something I would argue that should have been in CloudFormation in the first place), but it gets the job done, and, in addition, allows for all the flexibility of (take your pick of python/node/java). It should be noted that lambda functions cost money, but we're talking pennies here unless you create/delete your stacks multiple times per hour.
First step is to make a lambda function on this page that does nothing but take the input value and copy it to the output. We could have the lambda function do all sorts of crazy stuff, but once we have the identity function, anything else is easy. Alternatively we could have the lambda function being created in the stack itself. Since I use many stacks in 1 account, I would have a whole bunch of leftover lambda functions and roles (and all stacks need to be created with
--capabilities=CAPABILITY_IAM
, since it also needs a role.Create lambda function
index.handler
Then copy-paste the code below in the code field. The top of the function is the code from the cfn-response python module, that does only get auto-installed if the lambda-function is created through CloudFormation, for some strange reason. The
handler
function is pretty self-explanatory.You can now test the lambda function by selecting the "Test" button, and select "CloudFormation Create Request" as sample template. You should see in your log that the variables fed to it, are returned.
Use variable in your CloudFormation template
Now that we have this lambda function, we can use it in CloudFormation templates. First make note of the lambda function Arn (go to the lambda home page, click the just created function, the Arn should be in the top right, something like
arn:aws:lambda:region:12345:function:CloudFormationIdentity
).Now in your template, in the resource section, specify your variables like:
First I specify an
Identity
variable that contains the Arn for the lambda function. Putting this in a variable here, means I only have to specify it once. I make all my variables of typeCustom::Variable
. CloudFormation allows you to use any type-name starting withCustom::
for custom resources.Note that the
Identity
variable contains the Arn for the lambda function twice. Once to specify the lambda function to use. The second time as the value of the variable.Now that I have the
Identity
variable, I can define new variables usingServiceToken: !GetAtt [Identity, Arn]
(I think JSON code should be something like"ServiceToken": {"Fn::GetAtt": ["Identity", "Arn"]}
). I create 2 new variables, each with 2 fields: Name and Arn. In the rest of my template I can use!GetAtt [ClientBucketVar, Name]
or!GetAtt [ClientBucketVar, Arn]
whenever I need it.Word of caution
When working with custom resources, if the lambda function crashes, you're stuck for between 1 and 2 hours, because CloudFormation waits for a reply from the (crashed) function for an hour before giving up. Therefore it might be good to specify a short timeout for the stack while developing your lambda function.
You could use a nested stack which resolves all your variables in it's outputs, and then use
Fn::GetAtt
to read the outputs from that stackUse another stack as nested stack & pass in computed Parameter sample:
You might use nested templates in which you "resolve" all your variables in the outer template and pass them to another template.