CloudFormation のリソース仕様からコード生成してみる
はじめに
この間 AWS-CDK というプロジェクトが公開されていました。
aws.amazon.com github.com https://awslabs.github.io/aws-cdk/awslabs.github.io
まだ開発者プレビューという段階で正式な公開ではないようです。 中身はあまり詳しく見ていませんが、かなり便利に使えそうな印象がありました。 TypeScript, JavaScript, Java, .NET, Python などの言語で CloudFormation のリソースを記述して、これを元に色々なリソースを AWS 上に構築するのに使えるようです。
CloudFormation のテンプレートは YAML や JSON で書くことができますがなかなか大変です。 少し複雑なものを作ろうとするとリファレンスを参照しながら、トライアルアンドエラーでスタックを何度も立てたりして書いていた記憶があります。 色々な言語で IDE の補間やシンタックスチェックなどが効いた状態で書ければ便利になりそうです。 またプログラミング言語で書けるので、他のリソース情報を参照したりということもやりやすくなるのではないかと思います。
すこし調べていたところ CloudFormation のリソース仕様が公開されていると知りました。
cfn-lint のツールなどでもこの仕様が利用されているようです。 AWS-CDK の内部でもおそらくこの仕様からコード生成したりして利用されているのではないかと思います。
実際に使う場合には CDK などの公式のツールを使うのがいいと思いますが この仕様からコード生成するというのはそんなに難しくなくできそうなのと、ちょっと面白そうという気がしてコード生成するのを試してみました。
リソース仕様の構造
リソース仕様の構造についての詳しい説明はどこにあるかわかりませんでしたが、中身を見てみると意味はなんとなく理解できました。
一番上の階層に PropertyTypes
, ResourceTypes
, ResourceSpecificationVersion
があります。
{ "PropertyTypes": {...}, "ResourceTypes": {...}, "ResourceSpecificationVersion": "2.8.0" }
ResourceTypes
は色々なリソースの定義です。 PropertyTypes
はリソースの中で使われているプロパティの定義のようです。
ResourceSpecificationVersion
はこの仕様のバージョンだと思います。
基本の型など
基本の型 (PrimitiveType
) として Boolean
, Double
, Integer
, Long
, String
, Timestamp
, Json
があるようです。
このほかに Map
と Tag
は PrimitiveType
ではないですが定義なしで使われていて少し特殊な扱いのようでした。
Amazon API Gateway Deployment DeploymentCanarySettings - AWS CloudFormation
Map
は例えば上のような部分で使われていました。キーと値が両方文字列の連想配列のようです。
AWS CloudFormation Resource Tags タイプ - AWS CloudFormation
Tag
はキーと値の文字列のペアです。
それから List
タイプがあります。List
は配列です。List
の場合は ItemType
または PrimitiveItemType
で要素の型が書かれていました。
プロパティタイプ定義
プロパティタイプ のドキュメントはここにありました。
リソースプロパティタイプのリファレンス - AWS CloudFormation
プロパティタイプ定義は例えば下のような構造になっています。
... "AWS::IAM::User.Policy": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-policy.html", "Properties": { "PolicyDocument": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-policy.html#cfn-iam-policies-policydocument", "PrimitiveType": "Json", "Required": true, "UpdateType": "Mutable" }, "PolicyName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-policy.html#cfn-iam-policies-policyname", "PrimitiveType": "String", "Required": true, "UpdateType": "Mutable" } } }, ...
キーが "リソース名.プロパティ名"
になっています。
内容はドキュメントのリンクとプロパティ一覧で、おそらくプロパティタイプのプロパティは基本の型か Map か Tag に限られています。
Required
はプロパティが必須なものかどうかです。
UpdateType
はリソース更新で変更できるかどうかという意味だと思います。Immutable
, Mutable
, Conditional
のどれかが使われていました。
リソースタイプ定義
リソースタイプ のドキュメントはこれです。
AWS リソースプロパティタイプのリファレンス - AWS CloudFormation
リソースタイプ定義の例です。
... "AWS::IAM::User": { "Attributes": { "Arn": { "PrimitiveType": "String" } }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-user.html", "Properties": { "Groups": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-user.html#cfn-iam-user-groups", "DuplicatesAllowed": true, "PrimitiveItemType": "String", "Required": false, "Type": "List", "UpdateType": "Mutable" }, "LoginProfile": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-user.html#cfn-iam-user-loginprofile", "Required": false, "Type": "LoginProfile", "UpdateType": "Mutable" }, "ManagedPolicyArns": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-user.html#cfn-iam-user-managepolicyarns", "DuplicatesAllowed": false, "PrimitiveItemType": "String", "Required": false, "Type": "List", "UpdateType": "Mutable" }, "Path": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-user.html#cfn-iam-user-path", "PrimitiveType": "String", "Required": false, "UpdateType": "Mutable" }, "Policies": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-user.html#cfn-iam-user-policies", "DuplicatesAllowed": true, "ItemType": "Policy", "Required": false, "Type": "List", "UpdateType": "Mutable" }, "UserName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-user.html#cfn-iam-user-username", "PrimitiveType": "String", "Required": false, "UpdateType": "Immutable" } } } ...
プロパティタイプの定義とほぼ似た感じですがドキュメントリンクとプロパティ一覧以外に Attributes
があります。
Attributes
はリソース作成した時に付与される情報だと思います。 Attributes
に使われるのは基本の型のみで Required
と UpdateType
の指定はありません。
リソースタイプのプロパティは基本の型かプロパティタイプ定義で定義した型が使われています。
コード生成してみる
実験に使ったコードはこれです。
リソース定義を http-client で取得して JSON をパースし、テンプレートエンジンでコード生成という感じです。
仕様からコード生成するという流れはよくあるもののようで例えば Swagger や Protocol Buffers などでコード生成するというのを聞いたことがあります。 どちらもまだ使ったことがないのでこのような場合に使えるかわからないですが、また機会があればこの辺のツールも触ってみたいです。
生成したコードは下のような感じになりました。
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} module AWS.IAM.User ( Policy(..) , LoginProfile(..) , User(..) , resourceJSON ) where import AWS (Map, Tag) import Data.Text (Text) import Data.Aeson ((.=)) import qualified Data.Aeson as DA (FromJSON(..), ToJSON(..), Options(..), Value, defaultOptions, object) import qualified Data.Aeson.TH as DA (deriveJSON) data Policy = Policy { _PolicyPolicyDocument :: DA.Value , _PolicyPolicyName :: Text } deriving (Show, Eq) data LoginProfile = LoginProfile { _LoginProfilePassword :: Text , _LoginProfilePasswordResetRequired :: Maybe Bool } deriving (Show, Eq) data User = User { _UserGroups :: Maybe [Text] , _UserPath :: Maybe Text , _UserLoginProfile :: Maybe LoginProfile , _UserUserName :: Maybe Text , _UserManagedPolicyArns :: Maybe [Text] , _UserPolicies :: Maybe [Policy] } deriving (Show, Eq) $(DA.deriveJSON DA.defaultOptions { DA.fieldLabelModifier = drop 7 } ''Policy) $(DA.deriveJSON DA.defaultOptions { DA.fieldLabelModifier = drop 13 } ''LoginProfile) $(DA.deriveJSON DA.defaultOptions { DA.fieldLabelModifier = drop 5 } ''User) resourceJSON :: User -> DA.Value resourceJSON a = DA.object [ "Type" .= ("AWS::IAM::User" :: Text), "Properties" .= a ]
生成したコードを使って一応は Haskell のデータでリソースを定義して JSON を出力するということはできるんじゃないかと思います。 (実際に使うつもりはあまりなく実験してみたという感じです。)
まとめ
CloudFormation のリソース定義をつかってのコード生成をやってみました。 やろうと思えば色々なツールを作ったりもできるんじゃないかと思います。 仕様がプログラムから利用しやすい形で公開されているのはいいなと思いました。
この他にも IAM のアクションの仕様なども公開されるとうれしいと思います。 こちらのページで IAM アクションが検索できるサービスが公開されていました。
これは非公式なページで AWS ドキュメントから情報を抜き出したりして更新しているようです。 IAM アクション仕様が提供されていればこうしたツールも作りやすくなると思います。