

# 简单字符串替换宏的示例
<a name="macros-example"></a>

下面的示例演示了使用宏的过程：从在模板中定义宏，到为宏创建 Lambda 函数，再到在模板中使用宏。

在此示例中，我们创建一个简单的宏，它插入指定的字符串来替换已处理模板中的指定目标内容。然后我们将使用它在已处理模板的指定位置插入空白 `WaitHandleCondition`。

## 创建宏
<a name="macros-example-definiton"></a>

在使用宏之前，我们首先要完成两件事：创建执行所需模板处理的 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`，它们代表目标字符串及其所需的替换值。
+ 该宏可以对 `Type` 节点的内容进行操作，因为 `Type` 是引用该宏的 `Fn::Transform` 函数的同级节点。
+ 生成的 `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>

以下是 Lambda 函数的实际代码，它是 `JavaMacroFunc` 示例宏的底层。它迭代响应中包含的模板片段（无论是字符串、列表还是映射格式），从而查找指定的目标字符串。如果它找到指定的目标字符串，则 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"
  }
  ```

从 CloudFormation 发送的 `requestId` 匹配项，以及 `SUCCESS` 的 `status` 值表示 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"
        }
    }
}
```