

# シンプルな文字列置換マクロの例
<a name="macros-example"></a>

次の例では、マクロを使用するプロセスとして、テンプレートでのマクロの定義、マクロ用の Lambda 関数の作成、テンプレートでのマクロの使用について説明します。

この例では、処理されたテンプレートの指定されたターゲットコンテンツの代わりに指定された文字列を挿入する簡単なマクロを作成します。次に、それを使用して、処理されたテンプレート内の指定された場所に空白の `WaitHandleCondition` を挿入します。

## マクロの作成
<a name="macros-example-definiton"></a>

マクロを使用する前に、次の 2 つのことを完了する必要があります。目的のテンプレート処理を実行する Lambda 関数を作成してからマクロ定義を作成し、その Lambda 関数を CloudFormation で使用できるようにすることです。

次のサンプルテンプレートには、サンプルマクロの定義が含まれています。特定の AWS アカウント でマクロを利用できるようにするために、テンプレートからスタックを作成します。マクロ定義は、マクロ名、簡単な説明を指定し、このマクロがテンプレートで使用されたときに CloudFormation が呼び出す Lambda 関数の ARN を参照します。(エラーログ用の `LogGroupName` または `LogRoleARN` プロパティは含めていません。) 

この例では、このテンプレートから作成されたスタックの名前が `JavaMacroFunc` であるとします。マクロの `Name` プロパティはスタック名に設定されているので、結果として得られるマクロも `JavaMacroFunc` という名前になります。

```
AWSTemplateFormatVersion: 2010-09-09
  Resources:
    Macro:
      Type: AWS::CloudFormation::Macro
      Properties:
        Name: !Sub '${AWS::StackName}'
        Description: Adds a blank WaitConditionHandle named WaitHandle
        FunctionName: 'arn:aws:lambda:us-east-1:012345678910:function:JavaMacroFunc'
```

## マクロの使用
<a name="macros-example-usage"></a>

マクロを使用するには、`Fn::Transform` 組み込み関数を使用してそれをテンプレートに含めます。

以下のテンプレートを使用してスタックを作成すると、CloudFormation はサンプルマクロを呼び出します。基盤となる Lambda 関数は、指定された文字列を別の指定された文字列で置き換えます。この場合、結果は空白になり `AWS::CloudFormation::WaitConditionHandle` が処理済みテンプレートに挿入されます。

```
Parameters:
  ExampleParameter:
    Type: String
    Default: 'SampleMacro'

Resources:
  2a:
    Fn::Transform:
      Name: "JavaMacroFunc"
      Parameters:
        replacement: 'AWS::CloudFormation::WaitConditionHandle'
        target: '$$REPLACEMENT$$'
    Type: '$$REPLACEMENT$$'
```
+ 呼び出すマクロは、前のマクロ定義の例の `JavaMacroFunc` として指定されています。
+ このマクロには、`target` と `replacement` の 2 つのパラメータが渡されます。これらは、ターゲット文字列とその目的の置換値を表します。
+ `Type` はマクロを参照する `Fn::Transform` 関数の兄弟であるため、マクロは `Type` ノードの内容を操作できます。
+ 結果の `AWS::CloudFormation::WaitConditionHandle` は `2a` という名前になります。
+ テンプレートには、テンプレートパラメータ `ExampleParameter` も含まれています。このパラメータには、マクロからもアクセスできます (ただし、この場合は使用しません)。

## Lambda 入力データ
<a name="macros-example-request"></a>

CloudFormation がスタックの作成中にサンプルのテンプレートを処理するときに、`JavaMacroFunc` マクロ定義で参照されている Lambda 関数に次のイベントマッピングを渡します。
+ `region` : `us-east-1`
+ `accountId` : `012345678910`
+ `fragment` :

  ```
  {
    "Type": "$$REPLACEMENT$$"
  }
  ```
+ `transformId` : `012345678910::JavaMacroFunc`
+ `params` : 

  ```
  {
      "replacement": "AWS::CloudFormation::WaitConditionHandle",
      "target": "$$REPLACEMENT$$"
  }
  ```
+ `requestId` : `5dba79b5-f117-4de0-9ce4-d40363bfb6ab`
+ `templateParameterValues` :

  ```
  {
      "ExampleParameter": "SampleMacro"
  }
  ```

`fragment` には、マクロが処理できるテンプレートフラグメントを表す JSON が含まれています。このフラグメントは `Fn::Transform` 関数呼び出しの兄弟で構成されていますが、関数呼び出しそのものではありません。また、`params` にはマクロパラメータを表す JSON が含まれています。この場合は、置き換えとターゲットです 同様に、`templateParameterValues` には、テンプレート全体として指定されたパラメータを表す JSON が含まれています。

## Lambda 関数コード
<a name="macros-example-function"></a>

以下は、`JavaMacroFunc` マクロの基礎となる Lambda 関数の実際のコードです。指定されたターゲット文字列を探しながら、応答に含まれるテンプレートフラグメント (文字列、リスト、マップ形式など) を繰り返し処理します。指定されたターゲット文字列が見つかった場合、Lambda 関数はターゲット文字列を指定された置換文字列で置き換えます。そうでない場合は、関数はテンプレートフラグメントを変更しないままにします。それから、関数は CloudFormation に期待されるプロパティのマップを返します。詳細は後述します。

```
package com.macroexample.lambda.demo;

import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;

public class LambdaFunctionHandler implements RequestHandler<Map<String, Object>, Map<String, Object>> {

	private static final String REPLACEMENT = "replacement";
	private static final String TARGET = "target";
	private static final String PARAMS = "params";
	private static final String FRAGMENT = "fragment";
	private static final String REQUESTID = "requestId";
	private static final String STATUS = "status";
	private static final String SUCCESS = "SUCCESS";
	private static final String FAILURE = "FAILURE";
    @Override
    public Map<String, Object> handleRequest(Map<String, Object> event, Context context) {
        // TODO: implement your handler
    	final Map<String, Object> responseMap = new HashMap<String, Object>();
        responseMap.put(REQUESTID, event.get(REQUESTID));
        responseMap.put(STATUS, FAILURE);
    	try {
	        if (!event.containsKey(PARAMS)) {
	        	throw new RuntimeException("Params are required");
	        }
	    	
	        final Map<String, Object> params = (Map<String, Object>) event.get(PARAMS);
	        if (!params.containsKey(REPLACEMENT) || !params.containsKey(TARGET)) {
	        	throw new RuntimeException("replacement or target under Params are required");
	        }
	    	
	    	final String replacement = (String) params.get(REPLACEMENT);
	    	final String target = (String) params.get(TARGET);
	    	final Object fragment = event.getOrDefault(FRAGMENT, new HashMap<String, Object>());
	    	final Object retFragment;
	    	if (fragment instanceof String) {
	    		retFragment = iterateAndReplace(replacement, target, (String) fragment);
	    	} else if (fragment instanceof List) {
	    		retFragment = iterateAndReplace(replacement, target, (List<Object>) fragment);
	    	} else if (fragment instanceof Map) {
	    		retFragment = iterateAndReplace(replacement, target, (Map<String, Object>) fragment);
	    	} else {
	    		retFragment = fragment;
	    	}
	        responseMap.put(STATUS, SUCCESS);
	        responseMap.put(FRAGMENT, retFragment);
	        return responseMap;
    	} catch (Exception e) {
    		e.printStackTrace();
    		context.getLogger().log(e.getMessage());
    		return responseMap;
    	}
    }
    
    private Map<String, Object> iterateAndReplace(final String replacement, final String target, final Map<String, Object> fragment) {
    	final Map<String, Object> retFragment = new HashMap<String, Object>();
    	final List<String> replacementKeys = new ArrayList<>();
    	fragment.forEach((k, v) -> {
    		if (v instanceof String) {
    			retFragment.put(k, iterateAndReplace(replacement, target, (String)v));
    		} else if (v instanceof List) {
    			retFragment.put(k, iterateAndReplace(replacement, target, (List<Object>)v));
    		} else if (v instanceof Map ) {
    			retFragment.put(k, iterateAndReplace(replacement, target, (Map<String, Object>) v));
    		} else {
    			retFragment.put(k, v);
    		}
    	});
    	return retFragment;
    }

    private List<Object> iterateAndReplace(final String replacement, final String target, final List<Object> fragment) {
    	final List<Object> retFragment = new ArrayList<>();
    	fragment.forEach(o -> {
    		if (o instanceof String) {
    			retFragment.add(iterateAndReplace(replacement, target, (String) o));
    		} else if (o instanceof List) {
    			retFragment.add(iterateAndReplace(replacement, target, (List<Object>) o));
    		} else if (o instanceof Map) {
    			retFragment.add(iterateAndReplace(replacement, target, (Map<String, Object>) o));
    		} else {
    			retFragment.add(o);
    		}
    	});
    	return retFragment;
    }
    
    private String iterateAndReplace(final String replacement, final String target, final String fragment) {
    	System.out.println(replacement + " == " + target + " == " + fragment );
    	if (fragment != null AND_AND fragment.equals(target))
    		return replacement;
    	return fragment;
    }
}
```

## Lambda 関数レスポンス
<a name="macros-example-response"></a>

以下は、Lambda 関数が処理のために CloudFormation に返すマッピングです。
+ `requestId` : `5dba79b5-f117-4de0-9ce4-d40363bfb6ab`
+ `status` : `SUCCESS`
+ `fragment` :

  ```
  {
    "Type": "AWS::CloudFormation::WaitConditionHandle"
  }
  ```

`requestId` は CloudFormation から送信されたものと一致し、`status` 値 `SUCCESS` は Lambda 関数がリクエストに含まれるテンプレートフラグメントを正常に処理したことを示します。このレスポンスでは、`fragment` は元のテンプレートスニペットの代わりに処理されたテンプレートに挿入するコンテンツを表す JSON を含んでいます。

## 結果として得られる処理済みテンプレート
<a name="macros-example-processed"></a>

CloudFormation は、Lambda 関数から正常な応答を受け取ると、返されたテンプレートフラグメントを処理済みテンプレートに挿入します。

以下は、この例で処理されたテンプレートです。`JavaMacroFunc` マクロを参照した `Fn::Transform` 組み込み関数呼び出しは含まれなくなりました。Lambda 関数によって返されたテンプレートフラグメントは適切な場所に含まれ、その結果、`"Type": "$$REPLACEMENT$$"` は `"Type": "AWS::CloudFormation::WaitConditionHandle"` に置き換えられています。

```
{
    "Parameters": {
        "ExampleParameter": {
            "Default": "SampleMacro",
            "Type": "String"
        }
    },
    "Resources": {
        "2a": {
            "Type": "AWS::CloudFormation::WaitConditionHandle"
        }
    }
}
```