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
yreplacement
, que representan la cadena de destino y su valor de sustitución deseado. -
La macro puede funcionar con el contenido del nodo
Type
porqueType
tiene el mismo nivel que la funciónFn::Transform
que hace referencia a la macro. -
El elemento
AWS::CloudFormation::WaitConditionHandle
obtenido se denomina2a
. -
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" } } }