AWS CloudFormationのデプロイ済みスタックのテンプレートを取得

CloudFormationのデプロイ済みスタックのテンプレートをawscliでYAMLやJSONのフォーマットで取得する方法です。

`aws cloudformation get-template`コマンドでテンプレートを取得できるのですが、デプロイ時YAMLとJSONのどちらを使ったかによって返ってくるフォーマットが違い、毎回試行錯誤してしまうので、メモしておきます。

# テンプレートを取得するワンライナー

## YAMLでデプロイしたスタックをYAMLで取得

```console
$ aws cloudformation get-template --stack-name $STACKNAME | jq .TemplateBody -r
```

## JSONでデプロイしたスタックをJSONで取得

```console
$ aws cloudformation get-template --stack-name $STACKNAME | jq .TemplateBody
```

## YAMLでデプロイしたスタックをJSONで取得

```console
$ aws cloudformation get-template --stack-name $STACKNAME | jq .TemplateBody -r | yq

$ aws cloudformation get-template --stack-name $STACKNAME | ruby -rjson -ryaml -e 'puts JSON.pretty_generate(YAML.load(JSON.load(ARGF)["TemplateBody"]))'

$ aws cloudformation get-template --stack-name $STACKNAME | jq .TemplateBody -r | python -c 'import sys; import json; from awscli.customizations.cloudformation.yamlhelper import yaml_parse; print(json.dumps(yaml_parse(sys.stdin.read()), indent=4))'
```

1つ目と2つ目は `!GetAtt` みたいなのには非対応です。Pythonのboto3がある環境では、3つ目のようにがんばれば `!GetAtt` みたいなのに対応できます。


## JSONでデプロイしたスタックをYAMLで取得

```console
$ aws cloudformation get-template --stack-name $STACKNAME | yq .TemplateBody -y

$ aws cloudformation get-template --stack-name $STACKNAME | ruby -ryaml -e 'puts YAML.dump(YAML.load(ARGF)["TemplateBody"])'
```


# 試してみる

## テンプレートファイル

RDSのインスタンスを作るシンプルなテンプレートで試します。

```yaml
AWSTemplateFormatVersion: '2010-09-09'

Resources:
  SampleRDS:
    Type: AWS::RDS::DBInstance
    Properties:
      Engine: postgres
      EngineVersion: 12.5
      DBInstanceIdentifier: samplerdsinstance1
      DBInstanceClass: db.t3.micro
      StorageType: gp2
      AllocatedStorage: 20 # GiB
      DBName: mydb
      MasterUsername: myuser
      MasterUserPassword: mypassword

Outputs:
  SampleRDSEndpoint:
      Value: !GetAtt SampleRDS.Endpoint.Address
```

```json
{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Resources": {
    "SampleRDS": {
      "Type": "AWS::RDS::DBInstance",
      "Properties": {
        "Engine": "postgres",
        "EngineVersion": 12.5,
        "DBInstanceIdentifier": "samplerdsinstance2",
        "DBInstanceClass": "db.t3.micro",
        "StorageType": "gp2",
        "AllocatedStorage": 20,
        "DBName": "mydb",
        "MasterUsername": "myuser",
        "MasterUserPassword": "mypassword"
      }
    }
  },
  "Outputs": {
    "SampleRDSEndpoint": {
      "Value": {
        "Fn::GetAtt": ["SampleRDS", "Endpoint.Address"]
      }
    }
  }
}
```

## デプロイ

```console
$ aws cloudformation deploy --template-file template.yaml --stack-name sample-yaml
```

```console
$ aws cloudformation deploy --template-file template.json --stack-name sample-json
```

## aws cloudformation get-templateコマンド

デプロイ時のYAML/JSONでコマンドの出力がだいぶ違うんですよね。。。

YAMLの場合は、YAMLのコードがそのまま文字列になってJSONに埋め込まれている。JSONの場合は、そのままJSON。

```console
$ aws cloudformation get-template --stack-name sample-yaml
{
    "TemplateBody": "AWSTemplateFormatVersion: '2010-09-09'\n\nResources:\n  SampleRDS:\n    Type: AWS::RDS::DBInstance\n    Properties:\n      Engine: postgres\n      EngineVersion: 12.5\n      DBInstanceIdentifier: samplerdsinstance1\n      DBInstanceClass: db.t3.micro\n      StorageType: gp2\n      AllocatedStorage: 20 # GiB\n      DBName: mydb\n      MasterUsername: myuser\n      MasterUserPassword: mypassword\n\nOutputs:\n  SampleRDSEndpoint:\n      Value: !GetAtt SampleRDS.Endpoint.Address\n",
    "StagesAvailable": [
        "Original",
        "Processed"
    ]
}
```

```console
$ aws cloudformation get-template --stack-name sample-json
{
    "TemplateBody": {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Resources": {
            "SampleRDS": {
                "Type": "AWS::RDS::DBInstance",
                "Properties": {
                    "Engine": "postgres",
                    "EngineVersion": 12.5,
                    "DBInstanceIdentifier": "samplerdsinstance2",
                    "DBInstanceClass": "db.t3.micro",
                    "StorageType": "gp2",
                    "AllocatedStorage": 20,
                    "DBName": "mydb",
                    "MasterUsername": "myuser",
                    "MasterUserPassword": "mypassword"
                }
            }
        },
        "Outputs": {
            "SampleRDSEndpoint": {
                "Value": {
                    "Fn::GetAtt": [
                        "SampleRDS",
                        "Endpoint.Address"
                    ]
                }
            }
        }
    },
    "StagesAvailable": [
        "Original",
        "Processed"
    ]
}
```

## テンプレート取得

YAMLデプロイのスタックをYAMLで取得。YAML自体がJSONの文字列になっているのでjqコマンドに `-r` オプションを付ける。

```console
$ aws cloudformation get-template --stack-name sample-yaml | jq .TemplateBody -r
AWSTemplateFormatVersion: '2010-09-09'

Resources:
  SampleRDS:
    Type: AWS::RDS::DBInstance
    Properties:
      Engine: postgres
      EngineVersion: 12.5
      DBInstanceIdentifier: samplerdsinstance1
      DBInstanceClass: db.t3.micro
      StorageType: gp2
      AllocatedStorage: 20 # GiB
      DBName: mydb
      MasterUsername: myuser
      MasterUserPassword: mypassword

Outputs:
  SampleRDSEndpoint:
      Value: !GetAtt SampleRDS.Endpoint.Address
```

JSONデプロイのスタックをJSONで取得。

```console
$ aws cloudformation get-template --stack-name sample-json | jq .TemplateBody
{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Resources": {
    "SampleRDS": {
      "Type": "AWS::RDS::DBInstance",
      "Properties": {
        "Engine": "postgres",
        "EngineVersion": 12.5,
        "DBInstanceIdentifier": "samplerdsinstance2",
        "DBInstanceClass": "db.t3.micro",
        "StorageType": "gp2",
        "AllocatedStorage": 20,
        "DBName": "mydb",
        "MasterUsername": "myuser",
        "MasterUserPassword": "mypassword"
      }
    }
  },
  "Outputs": {
    "SampleRDSEndpoint": {
      "Value": {
        "Fn::GetAtt": [
          "SampleRDS",
          "Endpoint.Address"
        ]
      }
    }
  }
}
```

YAMLとJSONを変換したければ、冒頭に書いた通りyqコマンドやワンライナーで変換できます。


# コマンドのインストール

## jqコマンド

`jq` のインストールはUbuntuならば

```console
$ sudo apt install -y jq
```

CentOS 8ならば

```console
$ sudo yum install -y jq
```

## yqコマンド

`yq`はPythonで書かれており、pipでインストールできます。

```console
$ pip install yq
```

内部で`jq`を呼び出しているようで、`jq`がないと次のようなエラーになります。

```
yq: Error starting jq: FileNotFoundError: [Errno 2] No such file or directory: 'jq'. Is jq installed and available on PATH?
```