Ejemplo de macro: creación y uso de una macro
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.
Ejemplo de macro: plantilla de definició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.
nota
Más adelante, en este tema, examinaremos el código real para la función de Lambda.
La plantilla de ejemplo siguiente contiene la definición de nuestra macro de ejemplo. Para que la macro esté disponible en una cuenta CloudFormation 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). Para obtener más información, consulte AWS::CloudFormation::Macro.
En este ejemplo, se presupone que la pila creada a partir de esta plantilla se denomina JavaMacroFunc. Debido a que la propiedad Name
se establece en el nombre de la pila, la macro resultante 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'
Ejemplo de macro: plantilla de uso de una macro
Para utilizar nuestra macro en este caso, vamos a incluirla 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 un AWS::CloudFormation::WaitConditionHandle vacío que se inserta en la plantilla procesada.
Tenga en cuenta lo siguiente:
-
La macro para invocar se especifica como JavaMacroFunc, que es un 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 AWS::CloudFormation::WaitConditionHandle resultante 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).
Parameters: ExampleParameter: Type: String Default: 'SampleMacro' Resources: 2a: Fn::Transform: Name: "JavaMacroFunc" Parameters: replacement: 'AWS::CloudFormation::WaitConditionHandle' target: '$$REPLACEMENT$$' Type: '$$REPLACEMENT$$'
Ejemplo de macro: mapeo de evento de solicitud
Cuando CloudFormation procesa nuestra plantilla de ejemplo durante la creación de la pila, pasa el siguiente mapeo de evento a la función de Lambda a la que se hace referencia en la definición de macro JavaMacroFunc.
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.
-
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" }
Ejemplo de macro: código de 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; } }
Ejemplo de macro: respuesta de función de Lambda
A continuación, se presenta la asignación que la función de Lambda devuelve a CloudFormation para su procesamiento. 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.
-
requestId
5dba79b5-f117-4de0-9ce4-d40363bfb6ab
-
status
SUCCESS
-
fragment
{ "Type": "AWS::CloudFormation::WaitConditionHandle" }
Ejemplo de macro: plantilla procesada obtenida
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" } } }
Ejemplos de macros adicionales
Además del tutorial de esta guía, encontrará ejemplos de macros, incluido el código fuente y las plantillas, en nuestro repositorio de GitHub