CloudFromation+troposphere+AWS Lambdaでインフラ構築自動化

troposphereを利用したCloudFormationのテンプレ作成ツールを作成してみました。
また、作成したツールをLambda上で稼動させることでパラメータシートをS3に置くだけでテンプレが作成されるようにしてみました。

###JSON形式は書きづらい。
最近、業務でCloudFormationに触る機会がありました。
CloudFormationを使うにあたってネックになるのがJSON形式のファイルであることだと思います。

```json:
{
    "AWSTemplateFormatVersion": "2010-09-09",
    "Description": "hogehoge",
    "Resources": {
        "test1": {
            "Properties": {
                "CidrBlock": "10.1.0.0/16",
                "EnableDnsHostnames": "true",
                "EnableDnsSupport": "true",
                "Tags": [
                    {
                        "Key": "Name",
                        "Value": "test1"
                    }
                ]
            },
            "Type": "AWS::EC2::VPC"
        },
        "test2": {
            "Properties": {
                "CidrBlock": "10.2.0.0/16",
                "EnableDnsHostnames": "true",
                "EnableDnsSupport": "true",
                "Tags": [
                    {
                        "Key": "Name",
                        "Value": "test2"
                    }
                ]
            },
            "Type": "AWS::EC2::VPC"
        }
    }
}
```

VPC2つ作るだけでもこんな感じなので、これを手で書いていくのはなかなかの職人芸だと思います。

###troposphereとは
troposphereはそんなJSON形式のファイルを、Pythonで記述して作成するツールです。
https://github.com/cloudtools/troposphere
上記のコードもtroposphereを利用して記述すると次のように見やすく書くことができます。

```python:
import json
from troposphere import Tags,Template
from troposphere.ec2 import VPC
t = Template()
t.add_version("2010-09-09")
t.add_description("hogehoge")
t.add_resource(VPC(
              "test1",
              EnableDnsSupport="true",
              CidrBlock="10.1.0.0/16",
              EnableDnsHostnames="true",
              Tags=Tags(
                    Name="test1"
                    )
              )) 
t.add_resource(VPC(
              "test1",
              EnableDnsSupport="true",
              CidrBlock="10.2.0.0/16",
              EnableDnsHostnames="true",
              Tags=Tags(
                    Name="test2"
                    )
              ))
json_template = t.to_json()
print(json_template)                
```

基本的な使い方としてはadd_versionやadd_descriptionでバージョン名や説明を記載した後、add_resourceで作成するリソースをどんどん追加していくというような形です。
劇的に見やすくなるとはいえませんが、JSON形式で書くことに比べれば少しはましになるのではないかと思います。
また、単に見やすくなるだけでなく、Pythonのfor文やif文が使えるというところもポイントです。
複数のCIDRブロックやNameなどを配列にして渡すことでを記述することで大量のリソースを簡単に記述することが可能です。

```python:
import json
from troposphere import Tags,Template
from troposphere.ec2 import VPC
t = Template()
t.add_version("2010-09-09")
t.add_description("hogehoge")
VPC_CidrBlockList = ["10.1.0.0/16","10.2.0.0/16"]
VPC_TagsNameList = ["test1","test2"]
for (Address,Tag_Name) in zip(VPC_CidrBlockList,VPC_TagNameList):
t.add_resource(VPC(
              Tag_Name,
              EnableDnsSupport="true",
              CidrBlock=Address,
              EnableDnsHostnames="true",
              Tags=Tags(
                    Name=Tag_Name
                    )
              )) 
json_template = t.to_json()
print(json_template)                
```

###設定値を外部から持ってくる。
さて、上記の状態だとCIDRブロックやNameといった設定値をスクリプトにべた書きすることになりますがこれでは使いづらいです。
外部からパラメータを読み込んで、配列に格納して利用できるようにしてみましょう。
今回の場合、パラメータとして必要な情報は2種類(CIDRブロック、Name)なのでパラメータシートは以下のように作成し、テキストファイルとして保存します。
1行目　10.1.0.0/16,10.2.0.0/16
2行目　test1,test2

テキストファイルを作成したらまずはこのテキストファイルを開いて読み込みます。

```python:
f = open('test.txt')
test = f.read()
f.close
```

これでデータがtestに格納されました。しかし、この状態では1行目と2行目が分かれていません。
なので、splitlinesを利用して各行を配列として格納します。

```python:
test_list= test.splitlines()
```

これによって各行がそれぞれ要素となった配列test_listができます。
["10.1.0.0/16,10.2.0.0/16","test1,test2"]
さらにここでsplitを利用して各パラメータの配列を作成します。

```python:
VPC_CidrBlockList = test_list[0].split(',')
VPC_TagNameList = test_list[1].split(',')
```

以上でパラメータの配列を作成することができました。
これらの操作を取り込んで先ほどのスクリプトを修正すると以下のようになります。

```python:
import json
from troposphere import Tags,Template
from troposphere.ec2 import VPC

f = open('test.txt')
test = f.read()
f.close
test_list= test.splitlines()
VPC_CidrBlockList = test_list[0].split(',')
VPC_TagNameList = test_list[1].split(',')

t = Template()
t.add_version("2010-09-09")
t.add_description("hogehoge")
for (Address,Tag_Name) in zip(VPC_CidrBlockList,VPC_TagNameList):
t.add_resource(VPC(
              Tag_Name,
              EnableDnsSupport="true",
              CidrBlock=Address,
              EnableDnsHostnames="true",
              Tags=Tags(
                    Name=Tag_Name
                    )
              )) 
json_template = t.to_json()
print(json_template)                
```

これでパラメータシートに記載したパラメータに沿って複数VPCを作成できるようになりました。

###　AWS Lambdaを使ってみる。
Lambdaを利用するにあたって基本的な部分は変わりませんが、S3バケットからパラメータシートを取得する処理と、作成したテンプレをS3バケットにアップロードする処理を加えています。

```python:
# coding: utf-8

import json
import urllib
import boto3
from troposphere import Tags,Template
from troposphere.ec2 import VPC
from datetime import datetime

basename = datetime.now().strftime("%Y%m%d-%H%M%S")

print('Loading function')

s3 = boto3.resource('s3')

def lambda_handler(event, context):
        #print("Received event: " + json.dumps(event, indent=2))

        # Get the object from the event and show its content type
        bucket = event['Records'][0]['s3']['bucket']['name']
        key = urllib.unquote_plus(event['Records'][0]['s3']['object']['key']).decode('utf8')
        print ("buket:" + bucket)
        print ("key:" + key)
        obj = s3.Object(bucket,key)
        response= obj.get()
        body = response['Body'].read()
        body_list= body.splitlines()
        # u'VPCの作成'
        VPC_CidrBlockList = body_list[0].split(',')
        VPC_TagNameList= body_list[1].split(',')

        t = Template()
        t.add_version("2010-09-09")
        t.add_description("hogehoge")
        for (Address,Tag_Name) in zip(VPC_CidrBlockList,VPC_TagNameList):
            t.add_resource(VPC(
                  Tag_Name,
                  EnableDnsSupport="true",
                  CidrBlock=Address,
                  EnableDnsHostnames="true",
                  Tags=Tags(
                        Name=Tag_Name
                  )
            )) 
        json_template = t.to_json()
        bucket = s3.Bucket('template_test')
        obj = bucket.Object('json-template-' + basename + ' .txt')
        response = obj.put(
                       Body=json_template.encode('utf-8'),
                       ContentEncoding='utf-8',
                       ContentType='text/plane'
                    )
        print(json_template)                
```

変数にしておく項目を変更することで、より柔軟に設定することもできます(VPCだと「EnableDnsHostnames」とか)。
また、この要領で他のリソースについても記載しておけばVPCのみならず、サブネットやセキュリティグループなどのテンプレも自動作成できます。