AWS CloudFormation で組み込み関数を併用 

# 概要

AWS CloudFormation でテンプレートを作成していると、
`Parameters` と `Mappings` を連結したくなったり、
`!If` の中で [組み込み関数](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html
) を使いたくなったりと、
[組み込み関数](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html
) を併用して使いたくなることがたまにある。

使い方を忘れがちなので具体的な使用例を備忘録として記録します。
間違ってるところがあったら教えてください！

併用でなく単体で組み込み関数を使いたい場合は公式のドキュメント見た方がいいかも
[組み込み関数リファレンス - AWS CloudFormaion](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html
)

- 使用例の見方と注意点
  - YAML 形式で書いてます(一部 JSON を併用)
  - `Key: function` の Value と直後の `Key: result` の Value は同じことを表しています。

# 使用例

## `!If` の中で組み込み関数を使いたい！
`!If`, `!FindInMap`, `!Ref`, `!Sub` の併用

```yaml:使用例
Parameters:
  Env:
    Type: String
    Default: "Prod"
    AllowedValues: [ "Prod", "Stg" ]

Conditions:
  IsProd: !Equals [ !Ref Env, "Prod" ]   # 条件式 Env = Prod の意

Mappings:
  Tags:
    japanese:
      Prod: "honban"
      Stg: "sute-jingu"

Resources:
# ～省略～
      Tags:
        # 普通に !If
        - Key: function
          Value: !If
            - IsProd        # Conditions で設定した条件式 (この例では Parameters の Env が Prod なので 真)
            - "Production"  # 真の場合　(この例ではコッチ)
            - "Staging"     # 偽の場合
        - Key: result
          Value: "Production"

        # 普通に !FindInMap
        - Key: function
          Value: !FindInMap [Tags, Japanese, Prod]
        - Key: result
          Value: "honban"

        # If の中で !FindInMap を使う
        - Key: function
          Value: !If
            - IsProd
            - !FindInMap [Tags, Japanese, Prod]
            - "Staging"
        - Key: result
          Value: "honban"

        # !If の中で !FindInMap を使う(別パターン)
        - Key: function
          Value: !If
            - IsProd
            - !FindInMap
              - Tags
              - Japanese
              - Prod
            - "Staging"
        - Key: result
          Value: "honban"

        # !If の中で !FindInMap を使って !FindInMap の中で !Ref を使う
        - Key: function
          Value: !If
            - IsProd
            - !FindInMap
              - Tags
              - Japanese
              - !Ref Env
            - "Staging"
        - Key: result
          Value: "honban"

        # !If の中で !Sub    
        - Key: function
          Value: !If
            - IsProd
            - !Sub ${Env}-hoge
            - "Staging"
        - Key: result
          Value: "Prod-hoge"
```

## `Parameters` と `Mappings` を連結したい！
### ① `!Join` を使う
`!Join` と `!FindInMap` (と`!Ref`) の併用

```yaml:使用例
Parameters:
  Env:
    Type: String
    Default: "Prod"
Mappings:
  Tags:
    Service:
      Prod: "Business"
      Stg: "Game"
Resources:
# ～省略～
        # !Join の中で !FindInMap を使って、FindInMap の中で !Ref を使う
        - Key: function
          Value: !Join [ "-", [ !FindInMap [ Tags, Service, !Ref Env ], '1' ] ]
        - Key: result
          Value: "Business-1"

        # ↑の値を !Join で !Ref と連結 (Join の中の Join の中で FindInMap と !Refを使う)
        - Key: function
          Value: {'Fn::Join': [ ".", [ !Ref Env, !Join [ "-", [ !FindInMap [Tags, Service, !Ref Env ], '1' ] ] ] ] }
        - Key: result
          Value: "Prod.Business-1"
```

`!Join` わかりずらいので嫌い
`!Sub` の方が良い

### ② `!Sub` 使う (※こっちのがオススメ)
`!Sub` と `FindInMap` (と `!Ref`)を併用

```yaml:使用例
Parameters:
  Env:
    Type: String
    Default: "Prod"
Mappings:
  Tags:
    Service:
      Prod: "Business"
      Stg: "Game"
Resources:
# ～省略～
      Tags:
        - Key: function
          Value: !Sub
            - ${Env}.${hoge}-1
            - { hoge: !FindInMap [Tags, Service, !Ref Env] }
        - Key: result
          Value: "Prod.Bussiness-1"
```

`!Join` よりはマシだけどまだわかりずらい
いい方法ないかな。。。


## `!ImportValue` の呼び出し値をパラメーター化したい！
`!ImportValue` と `!Sub` の併用

```yaml:スタック名：prod-vpc-stack(参照元)
Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16

Outputs:
  VPC:
    Value: !Ref VPC
    Export:
      Name: !Sub ${AWS::StackName}-VpcId
```

```yaml:スタック名：prod-ec2-stack(参照先)
Parameters:
  Env:
    Type: String
    Default: "prod"
  ImportStackName:
    Type: String
    Default: "prod-vpc-stack"

Mappings:
  ImportValue:
    VpcId:
      prod: prod-vpc-stack-VpcId
      stg: stg-vpc-stack-VpcId

Resources:
#  ～省略～
        # 普通に !ImportValue
        - Key: function
          Value: ImportValue: prod-vpc-stack-VpcId

        # !ImportValue と !Sub を併用
        - Key: function
          Value: {'Fn::ImportValue': !Sub '${ImportStackName}-VpcId'}

        - Key: function
          Value: {'Fn::ImportValue': !Sub '${Env}-vpc-stack-VpcId'}

        # !ImportValue と !FindInMap を併用
        - Key: function
          Value: { "Fn::ImportValue" : { "Fn::FindInMap" : [ "ImportValue", "VpcId", !Ref Env ] } }

# いずれも prod-vpc-stack(参照元) の VPCID を取得できる
```

## `!GetAZs` を1行にしたい

アベイラビリティゾーンをベタ書きしてしまうと汎用性に欠けるし
[cfn-python-lint](https://github.com/aws-cloudformation/cfn-python-lint) で怒られてしまうので [!GetAZs](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-getavailabilityzones.html) を使う

こんなエラー
`W3010 Don't hardcode ap-northeast-1a for AvailabilityZones`


```yaml:使用例(ap-northeast-1の場合)

Resources:
# ～省略～
      tags:
        # 公式に書いてある方法だとこんな感じ
        - Key: function
          Value: !Select
          - 0
          - Fn::GetAZs: !Ref 'AWS::Region'
        # JSON 使って1行にしてみる
        - Key: function
          Value: { "Fn::Select" : ["0", { "Fn::GetAZs" : { "Ref" : "AWS::Region" } } ] }
        - Key: result
          Value: "ap-northeast-1a"

        - Key: function
          Value: { "Fn::Select" : ["1", { "Fn::GetAZs" : { "Ref" : "AWS::Region" } } ] }
        - Key: result
          Value: "ap-northeast-1c"

        - Key: function
          Value: { "Fn::Select" : ["2", { "Fn::GetAZs" : { "Ref" : "AWS::Region" } } ] }
        - Key: result
          Value: "ap-northeast-1d"
```

`{ "Fn::GetAZs" : { "Ref" : "AWS::Region" } }` が
`[ap-northeast-1a, ap-northeast-1c, ap-northeast-1d]` のような配列になっているイメージ。　※ `ap-northeast-1` の場合
その配列から、Selectで0,1,2番目を抜き出している。