cfn-language-discussion icon indicating copy to clipboard operation
cfn-language-discussion copied to clipboard

Fn::Map intrinsic function

Open benkehoe opened this issue 4 years ago • 6 comments

Resource Name

No response

Details

A lot of complaints about CloudFormation verbosity are things like having to replicate subnets for each availability zone. An Fn::Map function, which allowed basic parameterization of resources or properties, would allow for concise, semantically-meaningful expression of duplication.

A twitter thread on this topic

An example from here

Resources:
  Fn::Map::EfsMountTargets:
    # this assigns each item from Subnets to x while iterating
    Map: {x: !Ref Subnets}
    Type: AWS::EFS::MountTarget
    Properties:
      FileSystemId: !Ref EFSVolume
      SecurityGroups:
        - !Ref EFSSecurityGroup
      SubnetId: !Ref x
Outputs:
  Fn::Map::EfsMountIpAddress:
    Map: {x: !Ref Subnets}
    Description: !Sub "Ip Address for ${x}"
    # this might be better without the !Sub, assuming !GetAtt/Ref knows to resolve that in the Map
    Value: !GetAtt !Sub "EfsMountTargets.${x}.IpAddress"

This could be equivalent to

Resources:
  # the part after the . can be anything; as long as the same input keeps the same resource name
  # using the value as-is might help with linking mapped resources (see outputs)? 
  EfsMountTarget.eu-west-1a:
    Type: AWS::EFS::MountTarget
    Properties:
      FileSystemId: !Ref EFSVolume
      SecurityGroups:
        - !Ref EFSSecurityGroup
      SubnetId: eu-west-1a
  EfsMountTarget.eu-west-1b:
    Type: AWS::EFS::MountTarget
    Properties:
      FileSystemId: !Ref EFSVolume
      SecurityGroups:
        - !Ref EFSSecurityGroup
      SubnetId: eu-west-1a
Outputs:
  EfsMountIpAddress.eu-west-1a:
    Description: Ip Address for eu-west-1a
    Value: !GetAtt EfsMountTargets.eu-west-1a.IpAddress
  EfsMountIpAddress.eu-west-1b:
    Description: Ip Address for eu-west-1b
    Value: !GetAtt EfsMountTargets.eu-west-1b.IpAddress

The above assumes that Map was a first-class property of resources, for convenience. The general-purpose intrinsic function could look like this, including the ability to zip multiple lists together:

# with !Ref AZs being [a, b]
!Map:
  - AZ: !Sub "${AWS::Region}${x} - ${y}"
  - x: !Ref AZs
    y: [ one, two ]

would be equivalent to

- !Sub "${AWS::Region}a - one"
- !Sub "${AWS::Region}b - two"

benkehoe avatar Oct 14 '21 16:10 benkehoe

Agree that map is the way to go over loops. You should be able to prototype this as a transform, correct? That way, you could validate the notation on concrete examples.

Question: could it be possible to collect attributes from all mapped resources into a list?

For example, the following YAML expression:

!GetAttr EfsMountTarget.IpAddress.IpAddress

would become

[ !GetAttr EfsMountTarget.eu-west-1a.IpAddress.IpAddress, !GetAttr EfsMountTarget.IpAddress.eu-west-1b.IpAddress ] 

bjorg avatar Oct 16 '21 14:10 bjorg

@bjorg as long as you still have the list of things you're mapping, that should be possible.

Assuming the syntax of

# with !Ref AZs being [a, b]
!Map:
  - AZ: !Sub "${AWS::Region}${x}"
  - x: !Ref AZs

you could write that as

!Map:
  - Ip: !GetAtt EfsMountTarget.$x.IpAddress
  - x: !Ref AZs

sidenote @benkehoe : I forgot why that's not

# with !Ref AZs being [a, b]
!Map:
  - !Sub "${AWS::Region}${x}"
  - x: !Ref AZs

benbridts avatar Oct 16 '21 17:10 benbridts

Sometimes you need to map over an array where each element has more than one value. Maybe you need to map over an array of subnets that contains subnet IDs and AZs together. Each map iteration may need both of these values for different properties of the resource. Do you have a good idea of how to accomplish this?

txase avatar Oct 16 '21 18:10 txase

The idea was that the second parameter to Fn::Map is a mapping from key to list value, and there could be multiple keys, and then it would match the values in the list. Potentially there would need to be a parameter that controlled that behavior (e.g., zip to shortest, zip to longest, all combinations)

# with !Ref AZs being [a, b]
!Map:
  - AZ: !Sub "${AWS::Region}${x} - ${y}"
  - x: !Ref AZs
    y: [ one, two ]

would be equivalent to

- !Sub "${AWS::Region}a - one"
- !Sub "${AWS::Region}b - two"

benkehoe avatar Oct 16 '21 18:10 benkehoe

My suggestion would be to make the multi-prop capability explicit in the GH issue summary. Maybe as simple as making the examples multi-prop would work. It’s obvious in hindsight, but wasn’t on my first read!

txase avatar Oct 16 '21 18:10 txase

@benkehoe 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.

And we have an RFC for adding looping functionality to CFN template.

lejiati avatar May 10 '22 02:05 lejiati