Intrinsic functions¶
CloudFormation's intrinsic functions (Ref, Fn::Sub, Fn::Join and the rest) are how a template refers to things that
are only known at deploy time. Each has a builder in cfn_typed that returns the plain dict CloudFormation expects, so
the template stays reviewable data:
from cfn_typed import join, ref
join("/", ["", "orders", ref("Stage")]) # {"Fn::Join": ["/", ["", "orders", {"Ref": "Stage"}]]}
Every generated property is Expr[T], which means "a T, or any intrinsic". So an intrinsic goes anywhere a value goes,
and you never have to wrap one. Pseudo parameters are plain refs: ref("AWS::Region"), ref("AWS::NoValue").
The functions¶
| Builder | Emits | Use for |
|---|---|---|
ref(name) |
Ref |
a parameter, a resource, or a pseudo parameter |
get_att(resource, attr) |
Fn::GetAtt |
an attribute of a resource; attr may be a ref(...) |
sub(template, variables=None) |
Fn::Sub |
substitute ${Name} and ${Resource.Attr} in a string |
join(separator, parts) |
Fn::Join |
join strings; parts may itself be an intrinsic returning a list |
select(index, items) |
Fn::Select |
one item of a list |
split(delimiter, source) |
Fn::Split |
a string into a list |
find_in_map(map, key1, key2) |
Fn::FindInMap |
a value from the template's Mappings |
if_(condition, if_true, if_false) |
Fn::If |
choose by a named condition |
get_azs(region="") |
Fn::GetAZs |
the availability zones; the default is this region |
base64(value) |
Fn::Base64 |
encode a string, for example EC2 user data |
cidr(block, count, bits) |
Fn::Cidr |
carve subnets out of a block |
import_value(name) |
Fn::ImportValue |
a value exported by another stack |
arn(service, resource) |
Fn::Sub |
an ARN built from the partition, region and account, with arn.bucket(...), arn.function(...) and more; see policy statements |
if_ is how a property is left out: if_("IsProd", 5, ref("AWS::NoValue")) sets it in production and omits it elsewhere.
That is a condition in the template. For a property that depends on a value in your Python, see
Properties that are set only sometimes.
Conditions¶
The condition functions build a template's Conditions section, and are not values:
| Builder | Emits |
|---|---|
equals(a, b) |
Fn::Equals |
and_(...), or_(...) |
Fn::And, Fn::Or; CloudFormation takes between 2 and 10, and the builders refuse anything else |
not_(condition) |
Fn::Not |
condition_ref(name) |
{"Condition": name}, a reference to another condition |
Template["Conditions"] is typed, so a value that is not a condition function is an error. A resource takes a condition with
its condition="Name" keyword, and if_("Name", ...) takes the name as a string.
Everything in one template¶
This stack uses each function once. It is examples/intrinsics_demo.py,
type-checked and tested, and the rendered YAML is committed and checked by CI:
def build() -> cfn.Template:
params = cfn.Params()
stage = params.string("Stage", default="dev", allowed_values=["dev", "prod"])
network = params.string("NetworkStack", default="network", description="The stack that exports the VPC")
attribute = params.string("QueueAttribute", default="Arn", allowed_values=["Arn", "QueueName"])
return {
"AWSTemplateFormatVersion": "2010-09-09",
"Parameters": params.declarations(),
"Mappings": {"Sizes": {"dev": {"Timeout": "30"}, "prod": {"Timeout": "120"}}},
"Conditions": {
"IsProd": equals(stage, "prod"),
"IsNorthEurope": equals(ref("AWS::Region"), "eu-north-1"),
"ProdInNorthEurope": and_(condition_ref("IsProd"), condition_ref("IsNorthEurope")),
"NotProd": not_(condition_ref("IsProd")),
},
"Resources": {
"Queue": sqs.queue(
QueueName=sub("${Prefix}-${Stage}", {"Prefix": "orders"}), # Fn::Sub with variables
VisibilityTimeout=find_in_map("Sizes", stage, "Timeout"), # Fn::FindInMap
DelaySeconds=if_("IsProd", 5, ref("AWS::NoValue")), # Fn::If, leaving a property out
),
"QueueUrl": ssm.parameter(
Type="String",
Name=join("/", ["", "orders", stage, "queue-url"]), # Fn::Join
Value=ref("Queue"), # Ref
),
"QueueDetail": ssm.parameter(
Type="String", Name="/orders/detail",
Value=get_att("Queue", attribute), # Fn::GetAtt, with a Ref as the attribute
),
"FirstZone": ssm.parameter(Type="String", Name="/orders/first-zone", Value=select(0, get_azs())), # Fn::Select, Fn::GetAZs
"SecondItem": ssm.parameter(Type="String", Name="/orders/second", Value=select(1, split(",", "a,b,c"))), # Fn::Split
"Bootstrap": ssm.parameter(Type="String", Name="/orders/bootstrap", Value=base64(sub("echo ${Stage}"))), # Fn::Base64
"ProdOnly": ssm.parameter(Type="String", Name="/orders/prod-only", Value="yes", condition="ProdInNorthEurope"),
"Group": ec2.security_group(
GroupDescription="orders",
VpcId=import_value(sub("${NetworkStack}-VpcId")), # Fn::ImportValue
SecurityGroupIngress=[{
"IpProtocol": "tcp", "FromPort": 443, "ToPort": 443,
"CidrIp": select(0, cidr("10.0.0.0/16", 4, 8)), # Fn::Cidr
}],
),
},
}
How they render in YAML¶
to_yaml writes the short forms (!Ref, !Sub, !Join ...). CloudFormation does not allow a short form directly inside
another one, so where that happens the outer function stays in long form: in the example above Fn::Base64: !Sub ... and
Fn::ImportValue: !Sub .... An empty string after a short form is written out (!GetAZs ''), and a reference to another
condition inside and_, or_ or not_ stays Condition: Name. Parsing the YAML back gives the template you started with;
the test suite checks that for this example.
What the types do and do not know¶
The argument types are checked: join wants a list of strings or an intrinsic, sub wants string variables, and and_
wants condition functions. The types do not track what each function returns, only that it is an intrinsic. So
select(...), which returns one item, is accepted where a list is expected, and CloudFormation reports that at deploy time.
Keep cfn-lint in CI for those.