Skip to content

ForEach

ForEach makes one copy of a module for each item in a list. There are two forms: the ForEach key on a module, and Fn::ForEach in the Modules section.

The list must be known when cfnrain packages the template. It can be a comma-separated string, or !Ref to a CommaDelimitedList parameter with a Default. cfnrain uses the default value. It cannot use a value that CloudFormation resolves at deploy time.

The ForEach key

Add ForEach to a module entry. In the module's Properties, two placeholders are available:

Placeholder Value
$Identifier The current list item
$Index The position of the item in the list, from 0

There is no $MapValue placeholder. An old example in the Rain README used it, but the code never supported it.

cfnrain adds the index to the module name: Data0, Data1, Data2. So the resource Bucket becomes Data0Bucket, Data1Bucket and Data2Bucket.

The module, bucket-module.yaml:

Parameters:
  Name:
    Type: String

Resources:
  Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Ref Name
      VersioningConfiguration:
        Status: Enabled

Outputs:
  Arn:
    Value: !GetAtt Bucket.Arn

The parent template:

Parameters:
  Envs:
    Type: CommaDelimitedList
    Default: dev,stage,prod

Modules:
  Data:
    Source: ./bucket-module.yaml
    ForEach: !Ref Envs
    Properties:
      Name: billing-$Identifier-$Index

Outputs:
  ProdArn:
    Value: !GetAtt Data[2].Arn
  AllArns:
    Value: !Join
      - ","
      - !GetAtt Data[].Arn

The packaged template:

Parameters:
  Envs:
    Type: CommaDelimitedList
    Default: dev,stage,prod

Resources:
  Data0Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: billing-dev-0
      VersioningConfiguration:
        Status: Enabled

  Data1Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: billing-stage-1
      VersioningConfiguration:
        Status: Enabled

  Data2Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: billing-prod-2
      VersioningConfiguration:
        Status: Enabled

Outputs:
  ProdArn:
    Value: !GetAtt Data2Bucket.Arn

  AllArns:
    Value: !Join
      - ','
      - - !GetAtt Data0Bucket.Arn
        - !GetAtt Data1Bucket.Arn
        - !GetAtt Data2Bucket.Arn

Outputs of a loop

Reference Result
!GetAtt Data[2].Arn The output of one copy, by index
!GetAtt Data[prod].Arn The output of one copy, by list item
!GetAtt Data[].Arn A list with the output of every copy
${Data[0].Arn} in a !Sub The output of one copy, in a string

In another module with the same ForEach list, !GetAtt Data[$Index].Arn refers to the copy with the same index.

Fn::ForEach

Fn::ForEach::<LoopName> in the Modules section uses the syntax of the CloudFormation AWS::LanguageExtensions transform. It takes three items: an identifier name, the list, and a map from a module name to a module configuration. Use the identifier as ${Name} in the properties, or &{Name} in the module name.

If the identifier is in the module name, the copies use the list item in their names, such as BBFoo. If it is not, cfnrain adds the index, such as A0Foo.

The module:

Parameters:
  Name:
    Type: String
Resources:
  Foo:
    Type: A::B::C
    Properties:
      Name: !Ref Name
Outputs:
  Out1:
    Value: !GetAtt Foo.Arn

The parent template:

Modules:
  Fn::ForEach::A:
    - TheKey
    - A,B,C
    - A:
        Source: foreach-module.yaml
        Properties:
          Name: a-${TheKey}
  Fn::ForEach::B:
    - TheKey
    - A,B,C
    - B&{TheKey}:
        Source: foreach-module.yaml
        Properties:
          Name: b-${TheKey}
Outputs:
  TestGetAttA:
    Value: !GetAtt A[0].Out1
  TestGetAttB:
    Value: !GetAtt B[B].Out1

The packaged template:

Resources:
  A0Foo:
    Type: A::B::C
    Properties:
      Name: a-A
  A1Foo:
    Type: A::B::C
    Properties:
      Name: a-B
  A2Foo:
    Type: A::B::C
    Properties:
      Name: a-C
  BAFoo:
    Type: A::B::C
    Properties:
      Name: b-A
  BBFoo:
    Type: A::B::C
    Properties:
      Name: b-B
  BCFoo:
    Type: A::B::C
    Properties:
      Name: b-C
Outputs:
  TestGetAttA:
    Value: !GetAtt A0Foo.Arn
  TestGetAttB:
    Value: !GetAtt BBFoo.Arn

Fn::ForEach outside the Modules section is not processed by cfnrain. CloudFormation processes it if the template uses the AWS::LanguageExtensions transform.