ToJsonString関数を試してみた

CloudFormationテンプレート書いてますか?
今回は、使いどころが狭いもののテンプレートの保守性向上に効果バツグンのFn::ToJsonString関数を紹介します。

CloudFormationの関数

CloudFormationには、テンプレートに柔軟性を持たせるためのいろいろな組み込み関数が用意されています。特に使いどころが多い以下の関数については、使ったことがあるという方も多いと思います。

ところで、Fn::ToJsonStringってご存知でしょうか?
本稿執筆(2022年10月)時点では日本語ドキュメントには掲載されておらず、英語版に切り替えると存在が確認できます。

日本語版ページのメニューにはFn::ToJsonStringが存在しない英語版ドキュメントのアウトライン

Fn::ToJsonStringとは

Fn::ToJsonStringとは、端的に言うと「JSONオブジェクトを文字列化してくれる組み込み関数」です。
公式ドキュメントはこちら。標準的な関数とは位置づけが異なり、拡張機能を有効にすることで使えるようになります。
ドキュメントの記述を引用すると、こういった使い方に対して、

{
//...
    "Transform": "AWS::LanguageExtensions"
    //...
        "Fn::ToJsonString": {
            "key1": "value1",
            "key2": {
                "Ref": "ParameterName"
            }
        }
//...
}

関数の処理結果として下記の出力を返してくれます。

"{\"key1\":\"value1\"},{\"key2\":\"resolvedValue\"}"

どこで使うの?

自分は、CloudFormationのテンプレートを書くときは基本的にJSONで記述します。もちろん、Lambda関数コードなど埋め込みが難しいケースがありますので、あくまで「基本的には」です。

ただ、指定する値の型がJSON書式であるにもかかわらず、パラメータとしては文字列で与えるというところが存在します。テンプレートをJSONで記述していると、こういったパラメータについてはJSON形式で値を作った後に文字列に変換する必要が出てきます。つまりダブルクォートのエスケープ処理が発生します。短い文字列ならまだしも、埋め込みたいJSON書式の文字列が長くなってくると手間ですし、修正する気も起きなくなりますね。そんな時にFn::ToJsonStringを使うと記述がすっきりします。

こんなところで使うと便利です。
(他にも使いどころあるよ、という情報をお持ちの方、ぜひご連絡ください!)

どうやって使うの?

残念ながら、そのままでは使えません。
先述の関数のドキュメントの冒頭に以下のようなことが書いてあります。

Important:
You must use the AWS::LanguageExtensions transform to use the Fn::ToJsonString intrinsic function.

注意書き通り、まずはTransformセクションで拡張機能を使用することを宣言しましょう。
すでにTransformセクションがある場合はString型からArray型に直したうえで列記する書式になります。
(JSON派なのでサンプルは原則JSONで貫きます)

"Transform": "AWS::LanguageExtensions"

そのうえで、ToJsonStringの効果を適用したい場所で利用します。

今回はAWS公式ドキュメントにあるサンプルテンプレートを、Fn::ToJsonStringを使用した形式に変換してみました。
こちらがToJsonStringを使って記述したテンプレートです。

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Transform": "AWS::LanguageExtensions",
  "Resources": {
    "Dashboard": {
      "Type": "AWS::CloudWatch::Dashboard",
      "Properties": {
        "DashboardName": "Sample",
        "DashboardBody": {
          "Fn::ToJsonString": {
            "widgets": [
              {
                "type": "metric",
                "x": 0,
                "y": 0,
                "width": 12,
                "height": 6,
                "properties": {
                  "metrics": [
                    [
                      "AWS/EC2",
                      "CPUUtilization"
                    ]
                  ],
                  "period": 300,
                  "stat": "Average",
                  "region": "ap-northeast-1",
                  "title": "EC2 Instance CPU"
                }
              },
              {
                "type": "text",
                "x": 0,
                "y": 7,
                "width": 3,
                "height": 3,
                "properties": {
                  "markdown": "Hello world"
                }
              }
            ]
          }
        }
      }
    }
  }
}

比較用に、ToJsonStringを使わずに書いたものも掲載しておきます。つまりサンプルそのまんまですね。

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Resources": {
    "Dashboard": {
      "Type": "AWS::CloudWatch::Dashboard",
      "Properties": {
        "DashboardName": "Sample",
        "DashboardBody": "{\"widgets\":[{\"type\":\"metric\",\"x\":0,\"y\":0,\"width\":12,\"height\":6,\"properties\":{\"metrics\":[[\"AWS/EC2\",\"CPUUtilization\"]],\"period\":300,\"stat\":\"Average\",\"region\":\"ap-northeast-1\",\"title\":\"EC2 Instance CPU\"}},{\"type\":\"text\",\"x\":0,\"y\":7,\"width\":3,\"height\":3,\"properties\":{\"markdown\":\"Hello world\"}}]}"
      }
    }
  }
}

分かりやすさで言えば圧倒的に前者ではないでしょうか。分かりやすいということは保守性の向上にもつながりますので、AWSが提唱する「小さい更新を高頻度で」のスタイルにもマッチします。

代償

もちろん、注意しなければならないこともついて回ります。公式ドキュメントにも記載があります。

更新時に「以前のテンプレートを使用」が使えない

CloudFormation側から見た場合、パラメータと条件以外の要素でリソースが変化するため、コンソールにおける「以前のテンプレートを使用」やCLI等における--use-previous-templateオプションは使えません。毎回新しいテンプレートを送り込む形でスタックを更新する必要があります。

YAMLでは短縮型記法が使えない

YAMLでは!Refなどの短縮型記法が使えますが、AWS::LangusageExtentionsの追加によって初めて使えるようになる今回のような関数は、YAMLでの短縮型記法は使えません。ちゃんとFn::ToJsonStringと書きましょう。

複数の変換機能を利用する場合の記載順序に注意

AWS::Serverlessなど別の変換機能と併用する場合、AWS::LanguageExtensionsTransformセクション内で先に現れるように記述しましょう。

使えるのはResourcesセクションのみ

ConditionsOutputsなど他の場所では使えません。あきらめてください。

まとめ

今回は、普通にテンプレートを書いていても欲しくなりそうな機能であるFn::ToJsonString関数の使い方を紹介しました。みなさんも便利な関数を使ってテンプレートをより柔軟に使えるように改善していきましょう。ではまた!

投稿者プロフィール

hiroo
根っこはインフラ屋な古いおじさん。