JSON5でCloudFormationのテンプレートを書いてみる

2014.08.31

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、虎塚です。

CloudFormationのテンプレートをJSONで書く時、ファイルが巨大になってくると読み書きが大変ですよね。バリデーションや入力補完などのエディタサポートがもっと欲しいという話もありますが、今日はフォーマットの話にフォカースしてみます。

こんな要望はありませんでしょうか?

  • コメントを書きたい
    • ブロックコメントを書きたい
    • あわよくば日本語でコメントを書きたい
  • Description要素などの長文文字列が読みづらいので何とかしたい
  • 配列要素の末尾にカンマを許して欲しい
    • 要素をコピーアンドペーストするたびに、カンマを付けたり消したりしたくない

CloudFormationのテンプレートに限らず、JSONにはこれらの問題が付き物なので、拡張フォーマットがいくつか存在します。

今回はそれらの中の1つであるJSON5でCloudFormationのテンプレートを書いてみます。

JSON5でテンプレートを書く

元となるテンプレート

題材として、AWSが提供しているサンプルテンプレートを見てみましょう。

上記のテンプレート群から1つ選択して、JSON5に書き換えてみます。ここでは、EC2WithEBSPIOPs.templateを使いました。

Amazon EC2 インスタンスと、プロビジョンド IOP を備えた EBS ボリュームを作成します。 AWS CloudFormation サンプルテンプレート – アジアパシフィック(東京)リージョン

というテンプレートです(実際のJSONファイル)。

JSON5で書いてみる

上のテンプレートに、JSON5で使える要素を加えて改変すると、次のようになります。

{
  "AWSTemplateFormatVersion" : "2010-09-09",

  /*
    2014年8月 XYZ案件向けベーステンプレート
    -- 複数行コメントのテスト
  */
  // 複数行にわたる文字列をバックスペースで区切って改行するテスト
  "Description" : "AWS CloudFormation Sample Template EC2WithEBSPIOPs: \
Create an Amazon EC2 instance running the Amazon Linux AMI with \
a new EBS volume attached that has provisioned IOPs. \
The instance and the volume are pinned to the same availability zone. \
We recommend that you do untargeted launches rather than pinning instances this way.\
The AMI is chosen based on the region in which the stack is run. \
**WARNING** This template creates an Amazon EC2 instance and an EBS Volume. \
You will be billed for the AWS resources used if you create a stack from this template.",

  "Parameters" : {
    "KeyName": {
      "Description" : "Name of an existing EC2 KeyPair to enable SSH access to the instance",
      "Type": "String",
      "MinLength": "1",
      "MaxLength": "255",
      "AllowedPattern" : "[\\x20-\\x7E]*",
      "ConstraintDescription" : "can contain only ASCII characters."
    },

    "SSHFrom" : {
      "Description" : "Lockdown SSH access (default can be accessed from anywhere)",
      "Type" : "String",
      "MinLength": "9",
      "MaxLength": "18",
      "Default" : "0.0.0.0/0",
      "AllowedPattern" : "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
      "ConstraintDescription" : "must be a valid CIDR range of the form x.x.x.x/x."
    }
  },

  // 新しいAMIが出たら差し替えること -- 1行コメントのテスト
  "Mappings" : {
    "RegionMap" : {
      "us-east-1"      : { "AMI" : "ami-aecd60c7" },
      "us-west-2"      : { "AMI" : "ami-48da5578" },
      "us-west-1"      : { "AMI" : "ami-734c6936" },
      "eu-west-1"      : { "AMI" : "ami-6d555119" },
      "ap-southeast-1" : { "AMI" : "ami-3c0b4a6e" },
      "ap-southeast-2" : { "AMI" : "ami-bd990e87" },
      "ap-northeast-1" : { "AMI" : "ami-2819aa29" },

      // 配列の末尾要素にカンマを足すテスト(いわゆるケツカンマ)
      "sa-east-1"      : { "AMI" : "ami-fe36e8e3" },
    }
  },

  "Resources" : {
    "EC2Instance" : {
      "Type" : "AWS::EC2::Instance",
      "Properties" : {
        "SecurityGroups" : [ { "Ref" : "InstanceSecurityGroup" } ],
        "InstanceType" : "m1.large",
        "KeyName" : { "Ref" : "KeyName" },
        "ImageId" : { "Fn::FindInMap" : [ "RegionMap", { "Ref" : "AWS::Region" }, "AMI" ]},
        "EbsOptimized" : "true"
      }
    },

    "InstanceSecurityGroup" : {
      "Type" : "AWS::EC2::SecurityGroup",
      "Properties" : {
        "GroupDescription" : "Enable SSH access via port 22",
        "SecurityGroupIngress" : [ {
          "IpProtocol" : "tcp",
          "FromPort" : "22",
          "ToPort" : "22",
          "CidrIp" : { "Ref" : "SSHFrom" }
        } ]
      }
    },

    "MountPoint" : {
      "Type" : "AWS::EC2::VolumeAttachment",
      "Properties" : {
        "InstanceId" : { "Ref" : "EC2Instance" },
        "VolumeId"  : { "Ref" : "NewVolume" },
        "Device" : "/dev/sdh"
      }
    },

    "NewVolume" : {
      "Type" : "AWS::EC2::Volume",
      "Properties" : {
        "Size" : "100",
        "VolumeType" : "io1",
        "Iops" : "100",
        "AvailabilityZone" : { "Fn::GetAtt" : [ "EC2Instance", "AvailabilityZone" ]}
      }
    }
  },

  "Outputs" : {
    "InstanceId" : {
      "Description" : "InstanceId of the newly created EC2 instance",
      "Value" : { "Ref" : "EC2Instance" }
    }
  }
}
  • コメントを書いた
    • ブロックコメント(複数行コメント)を書いた
    • 日本語でコメントを書いた
  • 長文文字列を改行した
  • 配列要素の末尾にカンマを付けた

このような変更を加えました。このファイルをEC2WithEBSPIOPs.json5という名前で保存します。

JSON5のインストール

上のファイルをCloudFormationのテンプレートとして利用するには、JSON5からJSONへと変換する必要があります。

JSON5 to JSONのコンバートコマンドを使うために、JSON5をインストールしましょう。

Mac OS X 10.9.4 (13E28) で動作確認しました。

node.jsのインストール

Node.jsをインストールします。HomeBrewを使うと簡単です。

brew caskを使う場合、次のコマンドを実行します。

 brew cask install node

この時、Node.jsの実行環境と一緒にnpm(パッケージ管理ツール)もインストールされます。

JSON5のインストール

npmでJSON5をインストールします。グローバル領域にインストールする場合、次のコマンドを実行します。

% sudo npm install -g json5
Password:
/usr/local/bin/json5 -> /usr/local/lib/node_modules/json5/lib/cli.js
json5@0.2.0 /usr/local/lib/node_modules/json5
% which json5
/usr/local/bin/json5

JSON5をJSONへ変換する

次のコマンドを実行するだけで、JSON5からJSONへ変換できます。

json5 -c EC2WithEBSPIOPs.json5

EC2WithEBSPIOPs.jsonというファイルが、カレントディレクトリに生成されます。

確認

EC2WithEBSPIOPs.jsonを指定して、CloudFormationでスタックを作ってみましょう。

JSONファイルなので当たり前ではありますが、改変前のテンプレートと同様に、まったく問題なくスタックが作成されました。

ちょっとしたコツ

Macでのバックスラッシュの入力方法

複数行の文字列を改行するために、バックスラッシュを入力するとき、Macでエンマーク(¥)を使うとエラーになってしまいます。Macでバックスラッシュを入力するには、「optionキー + ¥」を入力します。

文字列の中にマルチバイト文字を入れてはいけない

JSON5ではコメントでマルチバイト文字が使えるので、つい文字列の中に日本語を書いてしまうことがありますが、CloudFormationのテンプレートとしては使えませんので気をつけましょう。

文字列中にマルチバイト文字がある状態でコンバートを実行しても成功するので、気づきにくいところです。注意しましょう。

数字や真偽値のダブルクォーテーションは外さない

数字や真偽値にダブルクォーテーションを付けなくてよいのも、JSON5の魅力のひとつです。しかし、それらも文字列として扱わなければ、CloudFormationのテンプレートとしてはエラーになります。

おわりに

誰かが作った巨大なテンプレートを読み解く時はもちろん、テンプレートを他の人へ引き継ぐ時などに、テンプレートへ注釈を書きたくなったら、JSON5を使ってみてもいいかもしれませんね。

それでは、また。