

# Ejemplo de macro sencilla de reemplazo de cadenas
<a name="macros-example"></a>

En el siguiente ejemplo, se explica con detalle el proceso de utilización de las macros, desde la definición de la macro en una plantilla hasta la creación de una función de Lambda para la macro y, a continuación, la utilización de la macro en una plantilla.

En este ejemplo, creamos un macro sencilla que inserta la cadena especificada en lugar del contenido de destino especificado en la plantilla procesada. Después la utilizaremos para insertar una `WaitHandleCondition` en blanco en la ubicación especificada en la plantilla procesada.

## Creación de una macro
<a name="macros-example-definiton"></a>

Antes de utilizar una macro, primero hay que hacer dos cosas: crear la función de Lambda que se encarga del procesamiento de la plantilla y poner la función de Lambda a disposición de CloudFormation creando una definición de macro.

La plantilla de ejemplo siguiente contiene la definición de nuestra macro de ejemplo. Para que la macro esté disponible en una Cuenta de AWS específica, tenemos que crear una pila a partir de la plantilla. La definición de la macro especifica el nombre de la macro, una breve descripción y hace referencia al ARN de la función de Lambda que CloudFormation invoca cuando esta macro se utiliza en una plantilla. (No hemos incluido una propiedad `LogGroupName` o `LogRoleARN` para el registro de errores). 

En este ejemplo se presupone que la pila creada a partir de esta plantilla se denomina `JavaMacroFunc`. Debido a que la propiedad `Name` de la macro se establece en el nombre de la pila, la macro resultante también se denomina `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'
```

## Uso de la macro
<a name="macros-example-usage"></a>

Para utilizar nuestra macro, la incluiremos en una plantilla utilizando la función intrínseca `Fn::Transform`.

Cuando creemos una pila utilizando la plantilla siguiente, CloudFormation llamará a nuestra macro de ejemplo. La función de Lambda subyacente sustituirá una cadena especificada por otra cadena especificada. En este caso, el resultado es que se inserta un `AWS::CloudFormation::WaitConditionHandle` en blanco en la plantilla procesada.

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

Resources:
  2a:
    Fn::Transform:
      Name: "JavaMacroFunc"
      Parameters:
        replacement: 'AWS::CloudFormation::WaitConditionHandle'
        target: '$$REPLACEMENT$$'
    Type: '$$REPLACEMENT$$'
```
+ La macro para invocar se especifica como `JavaMacroFunc`, que proviene del ejemplo de definición de macro anterior.
+ Se pasan dos parámetros a la macro, `target` y `replacement`, que representan la cadena de destino y su valor de sustitución deseado.
+ La macro puede funcionar con el contenido del nodo `Type` porque `Type` tiene el mismo nivel que la función `Fn::Transform` que hace referencia a la macro.
+ El elemento `AWS::CloudFormation::WaitConditionHandle` obtenido se denomina `2a`.
+ La plantilla también contiene un parámetro de plantilla, `ExampleParameter`, al que la macro también tiene acceso (pero no utiliza en este caso).

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

Cuando CloudFormation procesa nuestra plantilla de ejemplo durante la creación de la pila, pasa el siguiente mapeo de eventos a la función de Lambda a la que se hace referencia en la definición de la 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` contiene JSON que representa el fragmento de plantilla que la macro puede procesar. Este fragmento consta de los elementos de la llamada de función `Fn::Transform`, pero no la llamada de función en sí misma. Además, `params` contiene JSON que representa los parámetros de la macro. En este caso, la sustitución y el destino. Del mismo modo, `templateParameterValues` contiene JSON que representa los parámetros que se han especificado para la plantilla en su totalidad.

## Código de la función de Lambda
<a name="macros-example-function"></a>

A continuación se muestra el código real de la función de Lambda que subyace a la macro de ejemplo `JavaMacroFunc`. Se itera en el fragmento de código de plantilla incluido en la respuesta (ya sea en formato de cadena, lista o mapa), buscando la cadena de destino especificada. Si encuentra la cadena de destino especificada, la función de Lambda sustituye la cadena de destino por la cadena de sustitución especificada. Si no es así, la función deja el fragmento de código de plantilla sin cambiar. A continuación, la función devuelve un mapa de las propiedades esperadas, que se describen en detalle a continuación, en 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;
    }
}
```

## Respuesta de la función de Lambda
<a name="macros-example-response"></a>

A continuación, se presenta la asignación que la función de Lambda devuelve a CloudFormation para su procesamiento. 
+ `requestId` : `5dba79b5-f117-4de0-9ce4-d40363bfb6ab`
+ `status` : `SUCCESS`
+ `fragment` :

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

El `requestId` coincide con el que se envió desde CloudFormation y un valor `status` de `SUCCESS` indica que la función de Lambda ha procesado correctamente el fragmento de código de plantilla incluido en la solicitud. En esta respuesta, `fragment` contiene JSON que representa el contenido que debe insertarse en la plantilla procesada en lugar del fragmento de código de plantilla original.

## Plantilla procesada resultante
<a name="macros-example-processed"></a>

Una vez que CloudFormation recibe una respuesta correcta de la función de Lambda, se inserta el fragmento de código de plantilla devuelto en la plantilla procesada.

A continuación se muestra la plantilla procesada resultante para nuestro ejemplo. La llamada de función intrínseca `Fn::Transform` a la que hace referencia la macro `JavaMacroFunc` ya no está incluida. El fragmento de plantilla devuelto por la función de Lambda se incluye en la ubicación adecuada, con el resultado de que el contenido `"Type": "$$REPLACEMENT$$"` se ha sustituido con `"Type": "AWS::CloudFormation::WaitConditionHandle"`.

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