CFnで追加されたeachのつらみと対応策



SNS のトピック一覧をCFnで書いていると　ちょっとカッコよく登録できたらいいなとおもって、ついに♪───Ｏ（≧∇≦）Ｏ────♪ーーｷﾀｰと思って出てきた　CFnアップグレード　for each （繰り返して記載を省略かできる仕組み）

 これ：　https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-foreach.html

 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8216/8c6839f6-dfe3-6c4c-11eb-75fe40e45e90.png)

## では先輩、これをやってもらおうじゃないか（えらそうな後輩）

```yaml: SNS トピックのプロパティ内の値としてEachを使う・・・ことができない例）
## MappingセクションのSNSTopicメール送信先をカンマ区切りで　複数登録しようとした（がダメだった）
NotifyMailSNSTopic:
    Endpoint:
      p: ootsuki@dummymail-alert.local, yamada@dummymail-alert.local ##TODO 正しいメールアドレスに変える
      s: ootsuki@dummymail-alert.local, yamada@dummymail-alert.local ##TODO 正しいメールアドレスに変える
      d: ootsuki@dummymail-alert.local, yamada@dummymail-alert.local ##TODO 正しいメールアドレスに変える

```

## 任せとけ！　　おーつき先輩。　（と思ってできなかった例）

どうやら、each を使う時に３つ目の引数では　Key名とValueをとった関係で　Keyの中に　第一引数の変数名を含めた定義が必要になるらしい。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8216/aabe9884-07e1-38b7-028e-12d7901830f8.png)

```yaml: SNS トピックのプロパティ内の値としてEachを使う・・・ことができない例）

## 先頭部分で　以下の記載が必要です
Transform: 'AWS::LanguageExtentsions'
: (略)
### Resource セクションのSNS トピック内にある　Subscription（通知先一覧を回して登録しようとした・・・）
## CF の最後の最後実行前に「 fn::forEach の使い方invalid 」　と出てしまう
Subscription:
        'Fn::ForEach::MailAddresses':
            - mailAddress
            - "Fn::Split": [ ',', !FindInMap [ NotifyMailSNSTopic, Endpoint, !Ref EnvType ] ]
            - Endpoint: ${mailAddress}
              Protocol: email
```

だから、やるとしたら　サンプル例のようにSNSトピック自体を量産するように書く必要があるらしい・・・・
　悲しい例）　https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-foreach-example-resource.html#intrinsic-function-reference-foreach-example-replicate-resource

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8216/18de4989-9f14-1805-ee85-4ba1b899560d.png)

## Cloudformation の大好きな先輩でもダメなんですね・・・　（って言われたくないから対策を書いておこうと思います）

- SecurityGroup や　今回のような　SNSTopic をこういう使い回しできたらいいなと思うでしょう。

### [回避策]　SecurityGroupの場合は、　Ingress/Egressを使い追加でリソースを書こう！

``` yaml: SecurityGroup に追加のInboundを複数書く
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8216/fe147f6e-48ce-a8c4-ee31-45cac8885475.png)
### [TODO] コメント　SG　切り替えのため追加（後で詳しく書く）
        'Fn::ForEach::MailAddresses':
            - ipAddress
            - "Fn::Split": [ ',', ''1.2.3.4,2.3.4.5" ]    ## ここをParametersとかMappingsから参照
            - "FromPublicLimitAccessForALBRuleByPassAccessCondition_${ipAddress}:"
                Type: AWS::EC2::SecurityGroupIngress
                Condition: IsPublicAccessModeALBSecurityGroup
                Properties:
                    Description: Ingress from other containers in the same security group
                    GroupId: 
                        !Ref BaseServerSecurityGroup010 ### ここにまず最初の空のSGを定義しておいたものを参照する
                    IpProtocol: -1
                    FromIp: ${ipAddress}/32
                    TIp:  ${ipAddress}/32
```

### [回避策] 今回みたいなSNSTopincをどうしても複数作って管理したくない場合の例
```yaml: 複数件対応
## 先頭部分で　以下の記載が必要です
Transform: 'AWS::LanguageExtentsions'
: (略)
## Condition セクションに複数メールアドレス判定を追加しておく
## 通知
  NotifyMailSNSTopic:
    Type: AWS::SNS::Topic
    Properties:
      TopicName: 
        !Join [ '', [  !Ref SystemName, "-",!FindInMap [StackConfig, EnvLabel, !Ref EnvType], !Ref Mensu,  '-snstopic-notifyalarm010' ] ]
      Subscription:
            - Endpoint:
                  Fn::Select: [ 0,
                       Fn::Split: [ ',', !FindInMap [ NotifyMailSNSTopic, Endpoint, !Ref EnvType ] ] ]
              Protocol: email
            - Endpoint: 
                !If
                  - IsMultiMailaddress
                  - Fn::Select: [ 1,
                       Fn::Split: [ ',', !FindInMap [ NotifyMailSNSTopic, Endpoint, !Ref EnvType ] ] ]
                  - !Ref "AWS::NoValue"
              Protocol: email

## 通知（複数件対応いまできる方法で実現させる）
  NotifyMailSNSTopic:
    Type: AWS::SNS::Topic
    Properties:
      TopicName: 
        !Join [ '', [  !Ref SystemName, "-",!FindInMap [StackConfig, EnvLabel, !Ref EnvType], !Ref Mensu,  '-snstopic-notifyalarm010' ] ]
      Subscription:
            ## １個目は必ず入るので　SPlit した０個目のメールアドレスを追加する
            - Endpoint:
                  Fn::Select: [ 0,
                       Fn::Split: [ ',', !FindInMap [ NotifyMailSNSTopic, Endpoint, !Ref EnvType ] ] ]
              Protocol: email
            ## ２個目以降は select とSplitとIfを追加して書いていく
            - Endpoint: 
                !If
                  - IsMultiMailaddress
                  - Fn::Select: [ 1,　　　## ← ★★多少強引だけど、追加する数だけ n-1 でこの定義を追加していく★★
                       Fn::Split: [ ',', !FindInMap [ NotifyMailSNSTopic, Endpoint, !Ref EnvType ] ] ]
                  - !Ref "AWS::NoValue"
              Protocol: email
            ## n個目以降は select とSplitとIfを追加して書いていく
            - Endpoint: 
                !If
                  - IsMultiMailaddress
                  - Fn::Select: [ ★n-1個目★,　　　## ← ★★多少強引だけど、追加する数だけ n-1 でこの定義を追加していく★★
                       Fn::Split: [ ',', !FindInMap [ NotifyMailSNSTopic, Endpoint, !Ref EnvType ] ] ]
                  - !Ref "AWS::NoValue"
              Protocol: email
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8216/bb14f056-8c76-b8c3-bacd-3a2811f6e05a.png)

うぉっし！！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8216/895f24af-68f0-ceaa-2ff7-e11804aded73.png)


## まとめ：　Cloudforamtion でできて他でできないことはない！まだまだ　CDKに負けてらんねーぞ。

CFnの関数は創意工夫です。
色々な考え方をロジカルシンキングすればするほど回避策ができます。
これこそ　「エコシステム！」　ほんとCFnってよくできてる。　そしてこういった課題を乗り越えた時の快感と　技術力アップした感は半端ない！　みんなで　新しいCFnの可能性を作っていこうじゃありませんか。

こういう *選択肢* を残しているのがAWS のいいところです。