Skip to content

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.