

# Exemplo de macro simples de substituição de string
<a name="macros-example"></a>

O exemplo a seguir mostra todo o processo de uso das macros, desde a definição de uma macro em um modelo até a criação de uma função do Lambda para a macro e o uso da macro em um modelo.

Neste exemplo, criamos uma macro simples que insere a sequência especificada no lugar do conteúdo de destino especificado no modelo processado. E, em seguida, vamos usá-la para inserir um espaço em branco `WaitHandleCondition` no local especificado do modelo processado.

## Criar uma macro
<a name="macros-example-definiton"></a>

Antes de usar uma macro, é necessário concluir duas tarefas: criar a função do Lambda que executa o processamento de modelo desejado e, em seguida, disponibilizar essa função do Lambda ao CloudFormation, criando uma definição de macro.

O modelo de exemplo a seguir contém a definição da nossa macro de exemplo. Para tornar a macro disponível em uma Conta da AWS específica, crie uma pilha a partir do modelo. A definição da macro especifica o nome da macro, uma breve descrição e faz referência ao ARN da função Lambda que o CloudFormation invoca quando essa macro é usada em um modelo. (Não incluímos uma propriedade `LogGroupName` ou `LogRoleARN` para o registro em log de erros.) 

Neste exemplo, pressuponha que a pilha criada a partir desse modelo seja denominada `JavaMacroFunc`. Como a propriedade `Name` da macro é definida como o nome da pilha, a macro resultante também será denominada `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'
```

## Usar a macro
<a name="macros-example-usage"></a>

Para usar nossa macro, a incluímos em um modelo usando a função intrínseca `Fn::Transform`.

Quando você cria uma pilha usando o modelo a seguir, o CloudFormation chama nossa macro de exemplo. A função Lambda subjacente substitui uma sequência especificada por outra sequência especificada. Nesse caso, o resultado é que um `AWS::CloudFormation::WaitConditionHandle` em branco é inserido no modelo processado.

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

Resources:
  2a:
    Fn::Transform:
      Name: "JavaMacroFunc"
      Parameters:
        replacement: 'AWS::CloudFormation::WaitConditionHandle'
        target: '$$REPLACEMENT$$'
    Type: '$$REPLACEMENT$$'
```
+ A macro a ser invocada é especificada como `JavaMacroFunc`, do exemplo anterior de definição de macro.
+ A macro recebe dois parâmetros, `target` e `replacement`, que representam a sequência de destino e seu valor de substituição desejado.
+ A macro pode operar no conteúdo do nó `Type`, pois `Type` é um irmão da função `Fn::Transform` que referencia a macro.
+ O `AWS::CloudFormation::WaitConditionHandle` resultante se chama `2a`.
+ O modelo também contém um parâmetro de modelo, `ExampleParameter`, ao qual a macro também tem acesso (mas não usa neste caso).

## Dados de entrada do Lambda
<a name="macros-example-request"></a>

Quando o CloudFormation processa nosso exemplo de modelo durante a criação da pilha, ele transmite o mapeamento de eventos a seguir à função do Lambda referenciada na definição da macro `JavaMacroFunc`.
+ `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` contém um JSON que representa o fragmento de modelo que a macro pode processar. Esse fragmento consiste nos irmãos da chamada da função `Fn::Transform`, mas não a chamada de função propriamente dita. Além disso, `params` contém um JSON que representa os parâmetros da macro. Nesse caso, a substituição e o destino. Da mesma forma, `templateParameterValues` contém um JSON que representa os parâmetros especificados para o modelo como um todo.

## Código da função do Lambda
<a name="macros-example-function"></a>

O código a seguir é o código real para a função do Lambda subjacente ao exemplo da macro `JavaMacroFunc`. Ele itera sobre o fragmento de modelo incluído na resposta (seja em formato de sequência, lista ou mapa), procurando a sequência de destino especificada. Se ele encontrar a sequência de destino especificada, a função Lambda substituirá a sequência de destino pela sequência de substituição especificada. Caso contrário, a função deixará o fragmento de modelo inalterado. Em seguida, a função retorna um mapa das propriedades esperadas, discutido em detalhes abaixo, para o 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;
    }
}
```

## Resposta da função do Lambda
<a name="macros-example-response"></a>

Veja a seguir o mapeamento que a função do Lambda retorna ao CloudFormation para processamento. 
+ `requestId` : `5dba79b5-f117-4de0-9ce4-d40363bfb6ab`
+ `status` : `SUCCESS`
+ `fragment` :

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

As correspondências de `requestId` enviadas do CloudFormation e um valor `status` de `SUCCESS` indica que a função Lambda processou com êxito o fragmento de modelo incluído na solicitação. Nessa resposta, `fragment` contém um JSON que representa o conteúdo a ser inserido no modelo processado no lugar do trecho de modelo original.

## Modelo processado resultante
<a name="macros-example-processed"></a>

Depois que o CloudFormation recebe uma resposta bem-sucedida da função do Lambda, ele insere o fragmento de modelo retornado no modelo processado.

Veja a seguir o modelo processado resultante para o nosso exemplo. A chamada da função intrínseca `Fn::Transform` que referenciava a macro `JavaMacroFunc` não está mais incluída. O fragmento de modelo retornado pela função do Lambda está incluído no local apropriado, com o resultado de que o conteúdo `"Type": "$$REPLACEMENT$$"` foi substituído por `"Type": "AWS::CloudFormation::WaitConditionHandle"`.

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