

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 簡單字串取代巨集範
<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。在此案例中是 replacement 和 target。同樣地，`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"
        }
    }
}
```