Cloudformation-coverage-roadmap: Template Variables

Created on 2 Aug 2019  路  9Comments  路  Source: aws-cloudformation/cloudformation-coverage-roadmap

Sorry, this doesn't really fit the issue template. It would be awesome if the template had a section called something like "Variables" where I could alias a large !Sub or !Join to a smaller token.

AWSTemplateFormatVersion: 2010-09-09 # TODO Update
Parameters:
  DOMAIN:
    Type: String
    Description: "URL domain to use"
  STAGE:
    Type: String
    Description: "URL stage to use"
Variables:
  HostedZone:
    Type: String
    Value: !Sub "${STAGE}.${DOMAIN}"
Resources:
      MyAPI:
        Type: AWS::ApiGateway::RestApi
      APIDomainName:
        Type: AWS::ApiGateway::DomainName
        Properties:
          CertificateArn:
            Fn::ImportValue: "ACMCertArn"
          DomainName: !Ref HostedZone
      APIBasePathMapping:
        Type: AWS::ApiGateway::BasePathMapping
        Properties:
          DomainName: !Ref APIDomainName
          RestApiId: !Ref MyAPI
          Stage: Prod
      APIDomain:
        Type: AWS::Route53::RecordSetGroup
        Properties:
          HostedZoneName: !Sub "${HostedZone}."
          RecordSets:
            - Name: !Ref HostedZone
              Type: A
              AliasTarget:
                DNSName: !GetAtt APIDomainName.DistributionDomainName
                HostedZoneId: !GetAtt APIDomainName.DistributionHostedZoneId

This particular example has the variable inputs coming only from the Parameters, but it should allow me to ref elements created inside the template as long as it doesn't create a circular dependency.

enhancement

Most helpful comment

A developer was showing me a template today. They had around 15 template parameters, none of which were intended to be provided by the user of the template. Instead, they were using parameters to declare the set of "constants" the template relied on.

It looked like this:

Parameters:
  EventSchedule:
    Type: String
    Default: "cron(0 12 * * ? *)"
  FooValue:
    Type: AWS::SSM::Parameter::Value<String>
    Default: /sharedprefix/foo
  BarValue:
    Type: AWS::SSM::Parameter::Value<String>
    Default: /sharedprefix/bar

They went further, because they wanted to do this with Fn::ImportValue but they said it wasn't working in parameter defaults. So they were clever and created resources they could !Ref, using throwaway SSM parameters:

Resources:
  MyValue:
    Type: AWS::SSM::Parameter
    Properties:
      Type: String
      Value: !ImportValue MyValue

This would be significantly improved with a section dedicated to this purpose. I think of them as constants rather than variables:

Constants:
  EventSchedule: "cron(0 12 * * ? *)"
  ParamPrefix: "/sharedprefix"
  FooValue: 
    Type: AWS::SSM::Parameter::Value<String>
    Value: !Sub '${ParamPrefix}/foo'
  BarValue:
    Type: AWS::SSM::Parameter::Value<String>
    Value: !Sub '${ParamPrefix}/bar
  MyValue:
    Type: String
    Value: !ImportValue MyValue

All 9 comments

Agreed !

Closing this because @steven-cuthill-otm 's issue is a much better (more generic) version of this request.

https://github.com/aws-cloudformation/aws-cloudformation-coverage-roadmap/issues/86

I鈥檝e been convinced that they鈥檙e actually separate issues, thought they鈥檇 compliment each other nicely.

This one feature would make CloudFormation significantly easier to use.

A developer was showing me a template today. They had around 15 template parameters, none of which were intended to be provided by the user of the template. Instead, they were using parameters to declare the set of "constants" the template relied on.

It looked like this:

Parameters:
  EventSchedule:
    Type: String
    Default: "cron(0 12 * * ? *)"
  FooValue:
    Type: AWS::SSM::Parameter::Value<String>
    Default: /sharedprefix/foo
  BarValue:
    Type: AWS::SSM::Parameter::Value<String>
    Default: /sharedprefix/bar

They went further, because they wanted to do this with Fn::ImportValue but they said it wasn't working in parameter defaults. So they were clever and created resources they could !Ref, using throwaway SSM parameters:

Resources:
  MyValue:
    Type: AWS::SSM::Parameter
    Properties:
      Type: String
      Value: !ImportValue MyValue

This would be significantly improved with a section dedicated to this purpose. I think of them as constants rather than variables:

Constants:
  EventSchedule: "cron(0 12 * * ? *)"
  ParamPrefix: "/sharedprefix"
  FooValue: 
    Type: AWS::SSM::Parameter::Value<String>
    Value: !Sub '${ParamPrefix}/foo'
  BarValue:
    Type: AWS::SSM::Parameter::Value<String>
    Value: !Sub '${ParamPrefix}/bar
  MyValue:
    Type: String
    Value: !ImportValue MyValue

@benkehoe I've done similar things, including adding a AllowedValues to prevent modification:

Parameters:
  EventSchedule:
    Type: String
    Default: "cron(0 12 * * ? *)"
    AllowedValues: [ "cron(0 12 * * ? *)" ]

There's also a problem with the "constants as parameters with default values": changing the default for a parameter in a stack update does not change the value of that parameter, even if it its current value was provided by the default. Adding support for a separate section with different semantics would allow for updates to these values in stack updates.

I am right now copying and pasting a triple nested collection of macros to sanitize a parameter input (sometimes I want plain english, sometimes camelcase, and sometimes S3 suitable version of the same parameter). Being able to define this collection of nested macros once and reference elsewhere would save me copy / pasting and make the code more readable.

I like this idea of Constants, it would greatly improved my templates (note that ssm parameters are automatically created at the init of the project).

Parameters:
  ProjectName:
    Description: (Required) Project name (only ascii chars).
    Type: String
    AllowedPattern: '[\x20-\x7E]*'
    MinLength: 1
    ConstraintDescription: Only ASCII characters are allowed.

Resources:
  LoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Metadata:
      cfn-lint: {config: {ignore_checks: [E1019]}}
    Properties:
      Scheme: internal
      Name: !Sub
        - '${ProjectLower}-lb-${StackId}'
        - ProjectLower: {'Fn::Transform': {'Name': String, 'Parameters': {'InputString': !Ref ProjectName, 'Operation': Lower }}}
          StackId: !Select [0, !Split [-, !Select [2, !Split [/, !Ref AWS::StackId ]]]]
      Subnets: 
         - !Sub
            - '{{resolve:ssm:/abc/landing-zone/${RootAccountType}/sharedvpc-${AWS::Region}/subnets/${SubnetScope}/subnet-1}}'
            - RootAccountType: {'Fn::ImportValue': 'root-account-type'}
         - !Sub
            - '{{resolve:ssm:/abc/landing-zone/${RootAccountType}/sharedvpc-${AWS::Region}/subnets/${SubnetScope}/subnet-2}}'
            - RootAccountType: {'Fn::ImportValue': 'root-account-type'}
         - !Sub
            - '{{resolve:ssm:/abc/landing-zone/${RootAccountType}/sharedvpc-${AWS::Region}/subnets/${SubnetScope}/subnet-3}}'
            - RootAccountType: {'Fn::ImportValue': 'root-account-type'}
      SecurityGroups: 
        - !Sub 
          - '{{resolve:ssm:/abc/project/${ProjectLower}/security-group/sg-app-front}}'
          - ProjectLower: {'Fn::Transform': {'Name': String, 'Parameters': {'InputString': !Ref ProjectName, 'Operation': Lower }}}
      Tags:
        - Key: myTagA
          Value: {'Fn::Transform': {'Name': String, 'Parameters': {'InputString': !Ref ProjectName, 'Operation': Upper }}}
        - Key: myTagB
          Value: !Sub
            - '{{resolve:ssm:/abc/project/${ProjectLower}/map/app}}'
            - ProjectLower: {'Fn::Transform': {'Name': String, 'Parameters': {'InputString': !Ref ProjectName, 'Operation': Lower }}}
        - Key: myTagC
          Value: !Sub
            - '{{resolve:ssm:/abc/project/${ProjectLower}/map/value}}'
            - ProjectLower: {'Fn::Transform': {'Name': String, 'Parameters': {'InputString': !Ref ProjectName, 'Operation': Lower }}}

Was this page helpful?
0 / 5 - 0 ratings