CloudFormationを使ってCloudFormationマクロを作成する

#はじめに
本記事では、AWS CloudFormation管理コンソールを使って、CloudFormationマクロを作成する手順を説明しています。（初心者向け）

また、

本記事でサンプルとして紹介しているテンプレートのマクロの処理は、マクロに「Key」と「Description」を引数として渡すと、10桁のランダムな文字列を生成し、Systems Managerのパラメータストアに、キーとその値として10桁のランダムな文字列とその説明を登録する処理を提供しています。

本記事で掲載しているテンプレートの最新版は、下記に置いてます。
https://github.com/okubo-t/aws-cloudformation
#CloudFormationマクロとは
CloudFormationのテンプレートの標準的な定義だけでは実現できない処理を、
テンプレート内からLambda関数（マクロ）を呼び出すことで実現できるようにするCloudFormationの拡張機能のことです。
###参考
AWS ブログ
[AWS CloudFormation を AWS Lambda によるマクロで拡張する]
(https://aws.amazon.com/jp/blogs/news/cloudformation-macros/)

#設定手順
１ AWS CloudFormation管理コンソールから、スタックの作成をクリックします。
<img src="https://qiita-image-store.s3.amazonaws.com/0/283246/e0c3e7c3-f478-18c7-2dc2-7e669d1b399d.png"　 width=50%>

２ 後述のテンプレートを選択します。
<img src="https://qiita-image-store.s3.amazonaws.com/0/283246/060a4709-622a-2394-de5f-ac2103045137.png"　 width=50%>

３ 各パラメータを入力します。
<img src="https://qiita-image-store.s3.amazonaws.com/0/283246/4f2452a1-1670-a598-7fdc-44d3f8d1897a.png"　 width=70%>

|パラメータ名|用途|備考|
|:---|:---|:---|
|スタックの名前|テンプレートから作成するリソース一式の名前|例　prd-stack-vpc-20180801|
|CFnMacroName|作成するCloudFormation マクロの名前||

4 後続は、デフォルトのまま次へ次へで、作成します。
<font color="Red">作成する前に、下記のチェックをつけること</font>
<font color="Red">*AWS CloudFormation によってカスタム名のついた IAM リソースが作成される場合があることを承認します。*</font>
<img src="https://qiita-image-store.s3.amazonaws.com/0/283246/7b41654e-c324-186b-8a3f-788aa070b393.jpeg"　 width=50%>

５ 状況が CREATE COMPLETEになれば、CloudFormationマクロの作成が完了です。
<img src="https://qiita-image-store.s3.amazonaws.com/0/283246/f919b748-e62b-9f47-0eed-a3ab499405f0.png"　 width=50%>

6 Lambdaの管理コンソールで、作成したLambda関数（マクロ）が作成されているか確認できます。
<img src="https://qiita-image-store.s3.amazonaws.com/0/283246/ecf9cfeb-2d54-957f-062b-5c09580b4c24.png"　 width=70%>

#テンプレート
```cfn-macro-01.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: 
  CloudFormation Macro Create

# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------# 
Parameters:
  CFnMacroName:
    Type: String

# ------------------------------------------------------------#
#  LambdaExecutionRole
# ------------------------------------------------------------#        
Resources:
  LambdaExecutionRole:
    Type: "AWS::IAM::Role"
    Properties:
      RoleName: !Sub "${CFnMacroName}-LambdaExecutionRole"
      Policies:
        - PolicyName: !Sub "${CFnMacroName}-LambdaExecutionRole-Policy"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "logs:CreateLogGroup"
                  - "logs:CreateLogStream"
                  - "logs:PutLogEvents"
                Resource: "arn:aws:logs:*:*:*"

              - Effect: Allow
                Action:
                  - "ssm:PutParameter"
                Resource: "*"

      AssumeRolePolicyDocument: 
        Version: "2012-10-17"
        Statement: 
          - Sid: ""
            Effect: Allow
            Principal: 
              Service: "lambda.amazonaws.com"
            Action: "sts:AssumeRole"

# ------------------------------------------------------------#
#  Lambda Function for CloudFormation Macro
#  example : generate random string(10) and register SSM
# ------------------------------------------------------------#  
  LambdaFunction:
    Type: "AWS::Lambda::Function"
    Properties:
      FunctionName: !Ref CFnMacroName
      Role: !GetAtt LambdaExecutionRole.Arn
      Handler: index.handler

      Code:
        ZipFile: !Sub |
          import boto3
          import string
          import random

          ssm = boto3.client('ssm')

          def handler(event, context):
            key = event['params']['Key']
            description = event['params']['Description']
            randomstr = ''.join(random.choices(string.ascii_letters + string.digits, k=10))

            ssm.put_parameter(
              Name=key,
              Value=randomstr,
              Type='SecureString',
              Description=description
            )
            return {'requestId': event['requestId'], 'status': 'success', 'fragment': randomstr}

      Runtime: "python3.6"
      MemorySize: 128
      Timeout: 5

# ------------------------------------------------------------#
#  CloudFormation Macro
# ------------------------------------------------------------#  
  CFnMacro:
    Type: "AWS::CloudFormation::Macro"
    Properties:
      FunctionName: !Ref LambdaFunction
      Name: !Ref CFnMacroName
      Description: !Ref CFnMacroName 
```

# CloudFormationマクロの使い方
テンプレート内からCloudFormationマクロを呼び出す時の定義方法は、下記になります。
（この例では、RDSの MasterUserPasswordで使用しています。）

```YAML
Resources:
  DBInstance:
    Type: "AWS::RDS::DBInstance"
    Properties: 

      MasterUserPassword:
        "Fn::Transform":
        - Name: CFnMacroName　#呼び出したいCloudFormationマクロの名前
          Parameters:
            Key: MasterUserPassword #各引数
            Description: "MasterUserPassword for RDS" #各引数

      DBInstanceIdentifier: !Ref DBInstanceIdentifier
      Engine: MySQL
      EngineVersion: 5.7
      DBInstanceClass: !Ref DBInstanceClass
      AllocatedStorage: 100
      StorageType: gp2
      DBName: !Ref DBName

## 以下 省略
```