Exemple de macro de remplacement de chaîne simple - AWS CloudFormation

Les traductions sont fournies par des outils de traduction automatique. En cas de conflit entre le contenu d'une traduction et celui de la version originale en anglais, la version anglaise prévaudra.

Exemple de macro de remplacement de chaîne simple

L’exemple suivant passe en revue le processus d’utilisation des macros, de la définition de la macro dans un modèle jusqu’à l’utilisation de la macro dans un modèle, en passant par la création d’une fonction Lambda pour la macro.

Dans cet exemple, nous créons une macro simple qui insère la chaîne spécifiée à la place du contenu cible spécifié dans le modèle traité. Ensuite, nous l'utiliserons pour insérer un champ WaitHandleCondition vide dans l'emplacement spécifié dans le modèle traité.

Création d’une macro

Avant d'utiliser une macro, nous devons réaliser deux choses : créer la fonction Lambda qui effectue le traitement de modèle souhaité, puis mettre cette fonction Lambda à la disposition de CloudFormation en créant une définition de macro.

L'exemple de modèle suivant contient la définition de notre exemple de macro. Pour rendre la macro disponible dans un élément Compte AWS spécifique, créez une pile à partir du modèle. La définition de la macro spécifie le nom de macro et une brève description, et référence l'ARN de la fonction Lambda que CloudFormation appelle lorsque cette macro est utilisée dans un modèle. (Nous n’avons pas inclus de propriété LogGroupName ou LogRoleARN pour la journalisation des erreurs.)

Dans cet exemple, supposons que la pile créée à partir de ce modèle soit nommée JavaMacroFunc. Comme la propriété macro Name est définie sur le nom de la pile, la macro résultante est également nommée 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'

Utilisation de la macro

Pour utiliser notre macro, nous l’intégrons dans un modèle à l’aide de la fonction intrinsèque Fn::Transform.

Lorsque nous créons une pile en utilisant le modèle ci-dessous, CloudFormation appelle de notre exemple de macro. La fonction Lambda sous-jacente remplace une chaîne spécifiée par une autre chaîne spécifiée. Dans ce cas, le résultat est qu'un champ AWS::CloudFormation::WaitConditionHandle vide est inséré dans le modèle traité.

Parameters: ExampleParameter: Type: String Default: 'SampleMacro' Resources: 2a: Fn::Transform: Name: "JavaMacroFunc" Parameters: replacement: 'AWS::CloudFormation::WaitConditionHandle' target: '$$REPLACEMENT$$' Type: '$$REPLACEMENT$$'
  • La macro à invoquer est spécifiée comme JavaMacroFunc, qui provient de l’exemple de définition de macro précédent.

  • Deux paramètres sont transmis à cette macro, target et replacement, qui représentent la chaîne cible et sa valeur de remplacement souhaitée.

  • La macro peut fonctionner sur le contenu du nœud Type, car Type est un frère de la fonction Fn::Transform référençant la macro.

  • Le champ AWS::CloudFormation::WaitConditionHandle obtenu est nommé 2a.

  • Le modèle contient également un paramètre de modèle, ExampleParameter, auquel la macro a également accès (mais n'utilise pas dans ce cas).

Données d’entrée Lambda

Lorsque CloudFormation traite notre modèle d’exemple lors de la création de la pile, il transmet le mappage d’événements suivant à la fonction Lambda référencée dans la définition 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 contient du code JSON représentant le fragment de modèle que la macro peut traiter. Ce fragment comprend les frères de l'appel de fonction Fn::Transform, mais pas l'appel de fonction lui-même. En outre, params contient le code JSON représentant les paramètres de macro. Dans ce cas, replacement et target. De la même façon, templateParameterValues contient du code JSON représentant les paramètres spécifiés pour le modèle dans sa totalité.

Code de la fonction Lambda

Voici le code réel de la fonction Lambda sous-jacente à la macro d’exemple JavaMacroFunc. La fonction parcourt le fragment de modèle inclus dans la réponse (qu'elle soit dans un format de chaîne, de liste ou de mappage), à la recherche de la chaîne cible spécifiée. Si elle trouve la chaîne cible spécifiée, la fonction Lambda remplace la chaîne cible par la chaîne de remplacement spécifiée. Sinon, la fonction laisse le fragment de modèle inchangé. Ensuite, la fonction renvoie un mappage des propriétés attendues, présentées en détail ci-dessous, à 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; } }

Réponse de la fonction Lambda

Le mappage que la fonction Lambda renvoie à CloudFormation pour traitement est fourni ci-dessous.

  • requestId : 5dba79b5-f117-4de0-9ce4-d40363bfb6ab

  • status : SUCCESS

  • fragment :

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

Les correspondances requestId envoyées à partir de CloudFormation et la valeur status de SUCCESS dénotent que la fonction Lambda a traité avec succès le fragment de modèle inclus dans la demande. Dans cette réponse, fragment contient le code JSON représentant le contenu à insérer dans le modèle traité à la place de l'extrait de modèle d'origine.

Modèle traité obtenu

Une fois que CloudFormation a reçu une réponse positive de la fonction Lambda, il insère le fragment de modèle renvoyé dans le modèle traité.

Vous trouverez ci-dessous le modèle traité obtenu pour notre exemple. L’appel de fonction intrinsèque Fn::Transform qui faisait référence à la macro JavaMacroFunc n’est plus inclus. Le fragment de modèle renvoyé par la fonction Lambda est inclus dans l'emplacement approprié, de sorte que le contenu "Type": "$$REPLACEMENT$$" a été remplacé par "Type": "AWS::CloudFormation::WaitConditionHandle".

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