CloudFormationのForEach関数内でテンプレート内のリソースを参照する

# 1. はじめに
CloudFormationのForEach関数を使用してリソースを作成する際に、テンプレート内のリソースを参照する方法でハマったので、備忘録として記録

# 2. 何をしたいのか
通常CloudFormationテンプレート内でリソースを参照するときは、
```yaml
!Ref SampleVPC
```
のようにRef関数を使用すればいいが、ForEach関数を使用して作成リソースごとに参照するリソースを分ける場合は、`!Ref SampleVPC`このような参照方法が使えません。
今回は`AWS::EC2::SubnetRouteTableAssociation`の中で、テンプレート内のサブネットとルートテーブルを参照したいと思います。

# 3. テンプレート
```yaml
AWSTemplateFormatVersion: 2010-09-09
Description: Create VPC, Subent
Transform: AWS::LanguageExtensions

# Parameters Section
Parameters:
  System:
    Type: String
    Default: Sample
  VpcCidr:  
    Type: String
    Default: 10.0.0.0/24

# Mappings Section
Mappings:
  # SubnetMappings
  SubnetMappings:
    SampleSubnet01:
      Cidr: 10.0.0.0/27
      Az: ap-northeast-1c
      Number: 01
    SampleSubnet02:
      Cidr:  10.0.0.32/27
      Az: ap-northeast-1a
      Number: 02
    SampleSubnet03:
      Cidr: 10.0.0.64/27
      Az: ap-northeast-1c
      Number: 03
    SampleSubnet04:
      Cidr: 10.0.0.96/27 
      Az: ap-northeast-1a
      Number: 04
    SampleSubnet05:
      Cidr: 10.0.0.128/27
      Az: ap-northeast-1c
      Number: 05
    SampleSubnet06:
      Cidr: 10.0.0.160/27
      Az: ap-northeast-1a
      Number: 06
  # RouteTableMappings
  RouteTableMappings:
    SampleRouteTable01:
      Number: 01
    SampleRouteTable02:
      Number: 02
    SampleRouteTable03:
      Number: 03
  # RtbAssociationMappings
  RtbAssociationMappings:
    SampleRtbAssociation01:
      RouteTable: SampleRouteTable01
      Subnet: SampleSubnet01
    SampleRtbAssociation02:
      RouteTable: SampleRouteTable01
      Subnet: SampleSubnet02
    SampleRtbAssociation03:
      RouteTable: SampleRouteTable02
      Subnet: SampleSubnet03
    SampleRtbAssociation04:
      RouteTable: SampleRouteTable02
      Subnet: SampleSubnet04
    SampleRtbAssociation05:
      RouteTable: SampleRouteTable03
      Subnet: SampleSubnet05
    SampleRtbAssociation06:
      RouteTable: SampleRouteTable03
      Subnet: SampleSubnet06

# Resources Section
Resources:
# Sample Prd Vpc
  SampleVpc:
    Type: AWS::EC2::VPC
    Properties: 
      CidrBlock:  !Ref VpcCidr
      EnableDnsHostnames: true
      EnableDnsSupport: true
      Tags:
        - Key: Name
          Value: !Sub ${System}-vpc

# Subnet
# Private Subnets
  'Fn::ForEach::SubnetLoop':
    - SubnetName
    - - SampleSubnet01
      - SampleSubnet02
      - SampleSubnet03
      - SampleSubnet04
      - SampleSubnet05
      - SampleSubnet06
    - '${SubnetName}':
        Type: AWS::EC2::Subnet
        Properties:
          AvailabilityZone: !FindInMap [SubnetMappings, !Ref SubnetName, Az]
          CidrBlock: !FindInMap [SubnetMappings, !Ref SubnetName, Cidr]
          VpcId: !Ref SampleVpc
          Tags:
            - Key: Name
              Value: !Sub 
                - ${System}-subnet-${SubnetNumber}
                - SubnetNumber: !FindInMap [SubnetMappings, !Ref SubnetName, Number]

# 3 RouteTables
  'Fn::ForEach::RouteTableLoop':
    - RouteTableName
    - - SampleRouteTable01
      - SampleRouteTable02
      - SampleRouteTable03
    - '${RouteTableName}':
        Type: AWS::EC2::RouteTable
        Properties:
          VpcId: !Ref SampleVpc
          Tags:
            - Key: Name
              Value: !Sub 
                - ${System}-rtb-${RouteTableNumber}
                - RouteTableNumber: !FindInMap [RouteTableMappings, !Ref RouteTableName, Number]

# 6 RouteTable Association
  'Fn::ForEach::RtbAssociationLoop':
    - RtbAssociationName
    - - SampleRtbAssociation01
      - SampleRtbAssociation02
      - SampleRtbAssociation03
      - SampleRtbAssociation04
      - SampleRtbAssociation05
      - SampleRtbAssociation06
    - '${RtbAssociationName}':
        Type: AWS::EC2::SubnetRouteTableAssociation
        Properties:
        ### ↓↓↓ ハマったところ ↓↓↓
          RouteTableId: !GetAtt [!FindInMap [RtbAssociationMappings, !Ref RtbAssociationName, RouteTable], RouteTableId]
          SubnetId: !GetAtt [!FindInMap [RtbAssociationMappings, !Ref RtbAssociationName, Subnet], SubnetId]
```

# 4. ハマったところ
マッピングセクションからルートテーブルやサブネットの論理IDを参照するだけではうまくいかなかった。
そのため、GetAtt関数を使用し、その中でFindInMapで参照したルートテーブル（サブネット）名のIDを取得したことでうまくできた。
なお、ここの記述方法は以下でも可
```yaml
Properties:
  RouteTableId: !GetAtt
    - !FindInMap [RtbAssociationMappings, !Ref RtbAssociationName, RouteTable]
    - RouteTableId
  SubnetId: !GetAtt
    - !FindInMap [RtbAssociationMappings, !Ref RtbAssociationName, Subnet]
    - SubnetId
```

# 5. おわりに
当たり前だが、やはりMappingsが長くなった。
今回はサブネットが6つなので、楽でしたが。

関数の中に関数を書いて、さらにその中に関数を書くと、記述方法がわからなくなってくるので、慣れていきたいと思いました。