4 Expressions
Adam Pippin edited this page 3 years ago

Expressions

cfnpp contains a basic expression parser for evaluating conditions, mutating values, and more.

Expressions are composed of space separated values and operators, functions, etc are included using basic postfix notation.

(Why postfix? Because it's this is still largely a toy project and postfix is easy and fun to implement.)

That is, you push values onto the stack and then provide an operator to operate on the stack. That operator pops values off of the stack, and then pushes its result back on. Some basic examples of infix versus postfix:

  • 12 == 10 -- 12 10 eq
  • "foo" != "bar" -- "foo" "bar" eq not

Valid tokens consist of:

  • Numeric literals: any integer or float value in the format 1234 or 1234.5678
  • String literals: any value enclosed in double quotes ("); embedded quotes can be escaped with a backslash (\\)
  • Variables and Parameters: any non-quoted string consisting of letters and numbers (no leading digit) is interpreted as a reference to a variable or parameter
  • Operators: comparison/logical operators; currently supported:
    • eq: equal
    • not: inversion
    • and: boolean and
    • or: boolean or

Variables versus Parameter

Anything declared in the parameters map under cfnpp is interpreted as a reference to a parameter. Anything else is interpreted as a variable and uses the value available or causes an exception if it's not set.

Expressions Containing Parameters

cfnpp makes a best effort to simplify and solve any expression containing parameters as far as it can. Whatever remains it will attempt to convert into a CloudFormation condition to evaluate at the time of provisioning.

In some cases, cfnpp may be able to determine that the expression will always evaluate true or false regardless of the value of the referenced parameter in which case it will not create a condition.

For example, given:

cfnpp:
    parameters:
        LoadBalancerType: internal
    variables:
        CreateDnsRecords: true
        
Resources:
    ExternalDnsRecord: !if
        - LoadBalancerType "external" eq CreateDnsRecords and
        -
            Type: Route53::Record
            Zone: 1234
            Name: externalrecord
    InternalDnsRecord: !if
        - LoadBalancerType "internal" eq CreateDnsRecords or
        -
            Type: Route53::Record
            Zone: 1234
            Name: internalrecord

This will output:

Resources:
    ExternalDnsRecord:
        'Fn::If':
            - Condition0
            -
                Type: 'Route53::Record'
                Zone: 1234
                Name: externalrecord
    InternalDnsRecord:
        Type: 'Route53::Record'
        Zone: 1234
        Name: internalrecord
Metadata:
    Stack:
        - ''
Conditions:
    Condition0:
        'Fn::Equals':
            - external
            -
                Ref: LoadBalancerType

Note that the ExternalDnsRecords has simplified away the CreateDnsRecords and has created a Condition to check the value of the LoadBalancerType parameter. The InternalDnsRecords must be true since CreateDnsRecords is true so the value has simply been inserted.