Ejemplo de macro sencilla de reemplazo de cadenas - AWS CloudFormation

Ejemplo de macro sencilla de reemplazo de cadenas

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

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

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

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 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 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

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" } } }