aws-cloudformation / cfn-language-discussion

Language discussions for CloudFormation template language
https://aws.amazon.com/cloudformation/
Apache License 2.0
142 stars 13 forks source link

Template Variables #55

Open rhboyd opened 5 years ago

rhboyd commented 5 years ago

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.

ghost commented 5 years ago

Agreed !

rhboyd commented 5 years ago

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

rhboyd commented 5 years ago

I’ve been convinced that they’re actually separate issues, thought they’d compliment each other nicely.

woosanghun commented 5 years ago

This one feature would make CloudFormation significantly easier to use.

benkehoe commented 4 years ago

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
benbridts commented 4 years ago

@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 * * ? *)" ]
benkehoe commented 4 years ago

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.

jpbarto commented 3 years ago

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.

nocquidant commented 3 years ago

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 }}}
lejiati commented 2 years ago

@rhboyd Thank you very much for your feedback! Since this repository is focused on resource coverage, I'm transferring this issue over to a new GitHub repository dedicated to CloudFormation template language issues.

bjorg commented 2 years ago

This is hugely beneficial. Big +1! When can we get this?

The Value key should support 3 types of notation: string, list, and map. Also, Type should be optional.

Variables:
  MyTextVariable:
    Value: !Sub "${STAGE}.${DOMAIN}"
  MyListVariable:
    Value:
      - First Value
      - Second Value
  MyMapVariable:
    Value:
      First: Value
      Second: Other Value
benkehoe commented 2 years ago

I'd say that the value should be able to be any valid JSON type, and that putting, say, Type: Number on it should allow for conversion of a string containing a number into a proper number.

I also think there's a good argument that the type should also be able to be any CloudFormation resource type, where the value is a valid identifier for that type, and the resulting variable (after a call to the type's read handler) can be Ref'd and GetAtt'd like a resource. (I think this should be true for template parameters as well).

bjorg commented 2 years ago

As far as I can tell, the only literal type in CloudFormation is string. The various parameter types, such as Number are just input validators (as well as transforms, but that's out-of-scope anyway). These would not be appropriate for variables. For example, it wouldn't make sense to validate that a resource attribute is a number no larger than 10. How would such an assertion even work in practice?

Ref

Implementing Ref is pretty simple as it only requires in-lining with infinite recursion detection. It also needs support for Sub.

BEFORE:

Variables:
   MyMessage: !Sub "Hello ${Who}"
   Who: World
Resources:
  MyTopic:
    Type: AWS::SNS::Topic
    Properties:
      DisplayName: !Ref MyMessage

AFTER:

Resources:
  MyTopic:
    Type: AWS::SNS::Topic
    Properties:
      DisplayName: !Sub 
        - "Hello ${Who}"
        - Who: World

GetAtt

GetAtt is tricky, because it's only valid on resources. I'm not even sure how you would declare such a variable? You can't use Ref in the declaration since that has its own meaning.

Example, how would you declare that MyTopicResource is the actual resource and not its ARN (as returned by Ref)?

Variables:
  MyTopicResource: !Ref MyTopic
Resources:
  MyTopic:
    Type: AWS::SNS::Topic
gbudge commented 1 year ago

Apologies but I have looked around but couldn’t find if there’s any documentation how all of the CF repos hang together?

Looking for a primer that would allow me to figure out what repo/s would for example potentially need modification to support this feature.

Anyone know?

atkinsonm commented 1 year ago

Apologies but I have looked around but couldn’t find if there’s any documentation how all of the CF repos hang together?

Looking for a primer that would allow me to figure out what repo/s would for example potentially need modification to support this feature.

Anyone know?

The backend code of the CloudFormation service is private/closed source. AWS employees would need to make this change.

gbudge commented 1 year ago

That’s a shame. Thanks for replying.

kennytrytek-wf commented 9 months ago

AWS pseudo-parameters exist, and this issue reads to me as though user-defined pseudo-parameters would be sufficient, provided those pseudo-parameters allow for CFN template functions (!GetAtt, !Sub, etc.) to be used. I agree that would be helpful in reducing template size and making complicated-looking statements easy to read.

gbudge commented 4 months ago

@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 * * ? *)" ]

That's a clever idea.

gbudge commented 4 months ago

So it's been 5 years since this was requested and has a decent amount of thumbs up.

What's stopping AWS from implementing it after all this time and more importantly... how can we get some focus on it?

adrian-skybaker commented 4 months ago

I would suggest CDK, at this point cloudformation is the equivalent of assembly language.