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.