ちょっと話題の記事

[アップデート] CloudFormation で ForEach 組み込み関数を使ってループが定義出来るようになりました

2023.07.26

いわさです。

私は CloudFormation の更新履歴をよくチェックしているのですが、今朝Fn::ForEachという新しい組み込み関数が追加されていました。

えっ、ForEach !?

従来は CloudFormation で繰り返し処理・定義を行う機能は無かったので、どうしても必要な場合はあの手この手で頑張る必要がありました。

今回のアップデートでこれは今までは難しかった CloudFormation でのループ処理が実装出来るのでは、ということで試してみました。

2023.07.27 追記

今朝公式からのアップデートのアナウンスがありました。

前提条件:AWS::LanguageExtensions が必須

今回のアップデートは AWS::LanguageExtensions で使うことが出来ます。
詳細は次の記事をご確認いただければというところですが、簡単に言うと CloudFormation テンプレートの冒頭に拡張機能の使用を宣言することで標準の機能を拡張することが出来るようになります。

昨今 CloudFormation で便利な機能が追加されることがたまにあるのですが、たいていは AWS::LanguageExtensions によって提供されています。
このあたりとかもそうですね。

使ってみる

次の公式ドキュメントで使用方法が詳しく紹介されています。

利用にあたってはいくつか制限事項があります。大きなところだと Resources, Conditions, Outputs 内でのみ使えるよという点でしょうか。
次は Resources 内でFn::ForEachを使った例です。

AWSTemplateFormatVersion: 2010-09-09
Transform: 'AWS::LanguageExtensions'
Resources:
  'Fn::ForEach::HogeLoopName':
    - HogeItems
    - - AAA
      - BBB
      - CCC
    - 'Topic${HogeItems}':
        Type: 'AWS::SNS::Topic'
        Properties:
          TopicName: !Ref HogeItems

Fn::ForEach::HogeLoopName で任意のループ名を定義しています。
ループの名称はテンプレート内でユニークである必要があります。

引数としてループ要素の識別子、要素の配列値、繰り返しで出力する内容の 3 つを定義します。
上記の例では AAA/BBB/CCC の 3 つの要素の分だけ、AWS::SNS::Topicリソースを繰り返し定義しています。
ループ要素の識別子を使うことで、ループ出力内容で要素の値を参照することが出来ます。この例ではトピック名に要素の値を使っています。

普通にスタック作成するだけで良い

このテンプレートを使って、マネジメントコンソールからスタックを作成してみましょう。
スタックを作成したテンプレートタブからテンプレート内容を確認します。

「処理されたテンプレートの表示」を ON にすることで変換後のテンプレートを確認することが出来ます。(変換処理に成功している場合のみ)

確認してみると、3 つのAWS::SNS::Topicが定義されており、それぞれのTopicNameプロパティで要素値が使用されていますね。

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Resources": {
    "TopicAAA": {
      "Type": "AWS::SNS::Topic",
      "Properties": {
        "TopicName": "AAA"
      }
    },
    "TopicBBB": {
      "Type": "AWS::SNS::Topic",
      "Properties": {
        "TopicName": "BBB"
      }
    },
    "TopicCCC": {
      "Type": "AWS::SNS::Topic",
      "Properties": {
        "TopicName": "CCC"
      }
    }
  }
}

デザイナーで見てみる

CloudFormation ではアップロードしたテンプレートや、作成後のスタックをデザイナー機能を使って視覚的にリソースを確認することが出来ます。
今回の機能を使って作成するとどのように見えるのでしょうか。

スタック作成前はただのカスタムリソース

テンプレートアップロード直後、スタック作成前のデザイナー画面では次のようにカスタムリソースとして認識されていました。
なるほど、内部的にはカスタムリソースが使われてるのか。

スタック作成後は展開された標準リソース

しかし、スタック作成後にデザイナーを見てみると、期待どおり展開された標準リソースを確認することが出来ました。
今回の場合であれば SNS トピックを 3 つ確認することが出来ます。

パラメータを使って要素を設定してみる

要素についてはリスト値であれば良いのでパラメータで定義してみます。

AWSTemplateFormatVersion: 2010-09-09
Transform: 'AWS::LanguageExtensions'
Parameters:
  HogeItemNames:
    Type: List<String>
    Default: AAA,BBB,CCC
Resources:
  'Fn::ForEach::HogeLoopName':
    - HogeItems
    - !Ref HogeItemNames
    - 'Topic${HogeItems}':
        Type: 'AWS::SNS::Topic'
        Properties:
          TopicName: !Ref HogeItems

期待どおり動的なパラメータに従ってリソースを展開することが出来ました。

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Parameters": {
    "HogeItemNames": {
      "Type": "List<String>",
      "Default": "AAA,BBB,CCC"
    }
  },
  "Resources": {
    "TopicAAA": {
      "Type": "AWS::SNS::Topic",
      "Properties": {
        "TopicName": "AAA"
      }
    },
    "TopicBBB": {
      "Type": "AWS::SNS::Topic",
      "Properties": {
        "TopicName": "BBB"
      }
    },
    "TopicCCC": {
      "Type": "AWS::SNS::Topic",
      "Properties": {
        "TopicName": "CCC"
      }
    }
  }
}

出力で使用してみる

冒頭紹介したように、Fn::ForEeachは Resources の他に Conditions と Outputs でも使うことが出来ます。
Outputs でも使ってみましょう。

AWSTemplateFormatVersion: 2010-09-09
Transform: 'AWS::LanguageExtensions'
Parameters:
  HogeItemNames:
    Type: List<String>
    Default: AAA,BBB,CCC
Resources:
  'Fn::ForEach::HogeLoopName1':
    - HogeItems
    - !Ref HogeItemNames
    - 'Topic${HogeItems}':
        Type: 'AWS::SNS::Topic'
        Properties:
          TopicName: !Ref HogeItems
Outputs:
  'Fn::ForEach::HogeLoopName2':
    - HogeItems
    - !Ref HogeItemNames
    - '${HogeItems}Output':
        Descriotion: hoge
        Value: !GetAtt  [!Sub 'Topic${HogeItems}', TopicArn]

上記からスタックを作成してみると、次のように処理されたテンプレートでは Outputs 内で繰り返し定義がされています。

実際に出力されたパラメータを確認してみると、ForEach で作成されたトピックの ARN が期待どおり出力されていることが確認出来ました。

ループの入れ子を試してみる

試してみたところ、ループの中で別のループを定義させることも出来ました。
3 x 3 の要素で定義しています。

AWSTemplateFormatVersion: 2010-09-09
Transform: 'AWS::LanguageExtensions'
Parameters:
  HogeItemNames1:
    Type: List<String>
    Default: AAA,BBB,CCC
  HogeItemNames2:
    Type: List<String>
    Default: 111,222,333
Resources:
  'Fn::ForEach::HogeLoopName1':
    - HogeItems1
    - !Ref HogeItemNames1
    - 'Fn::ForEach::HogeLoopName2':
      - HogeItems2
      - !Ref HogeItemNames2
      - 'Topic${HogeItems1}${HogeItems2}':
          Type: 'AWS::SNS::Topic'
          Properties:
            TopicName: !Sub Topic${HogeItems1}${HogeItems2}

処理されたテンプレートを確認してみると...

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Parameters": {
    "HogeItemNames1": {
      "Type": "List<String>",
      "Default": "AAA,BBB,CCC"
    },
    "HogeItemNames2": {
      "Type": "List<String>",
      "Default": "111,222,333"
    }
  },
  "Resources": {
    "TopicAAA111": {
      "Type": "AWS::SNS::Topic",
      "Properties": {
        "TopicName": "TopicAAA111"
      }
    },
    "TopicAAA222": {
      "Type": "AWS::SNS::Topic",
      "Properties": {
        "TopicName": "TopicAAA222"
      }
    },
    "TopicAAA333": {
      "Type": "AWS::SNS::Topic",
      "Properties": {
        "TopicName": "TopicAAA333"
      }
    },
    "TopicBBB111": {
      "Type": "AWS::SNS::Topic",
      "Properties": {
        "TopicName": "TopicBBB111"
      }
    },
    "TopicBBB222": {
      "Type": "AWS::SNS::Topic",
      "Properties": {
        "TopicName": "TopicBBB222"
      }
    },
    "TopicBBB333": {
      "Type": "AWS::SNS::Topic",
      "Properties": {
        "TopicName": "TopicBBB333"
      }
    },
    "TopicCCC111": {
      "Type": "AWS::SNS::Topic",
      "Properties": {
        "TopicName": "TopicCCC111"
      }
    },
    "TopicCCC222": {
      "Type": "AWS::SNS::Topic",
      "Properties": {
        "TopicName": "TopicCCC222"
      }
    },
    "TopicCCC333": {
      "Type": "AWS::SNS::Topic",
      "Properties": {
        "TopicName": "TopicCCC333"
      }
    }
  }
}

9 つの要素が展開されていますね。

規則に基づいて大量にリソースを作成したい時に使えそうですが、CloudFormation の各クォータについては処理された後のテンプレート内容に基づくのでテンプレートサイズなどに注意してください。

さいごに

本日は CloudFormation で ForEach 組み込み関数を使ってループが定義出来るようになったので試してみました。

分岐や繰り返しは不具合のもとになりやすいので注意して使いたいところです。

個別の環境のリソースを管理する場合には特殊なことをせずに定義したほうが管理しやすくて良いかなと個人的には思ってますが、一方でソリューション実装のように汎用的なテンプレートを作る必要がある場合は動的な柔軟性が求められる場合も多いので、そういったシーンでは活用用途ありそうですね。