Esempio di una macro semplice di sostituzione delle stringhe - AWS CloudFormation

Esempio di una macro semplice di sostituzione delle stringhe

L’esempio seguente descrive il processo di utilizzo di macro, dalla definizione dalla macro in un modello alla creazione di una funzione Lambda per la macro e all’utilizzo della macro in un modello.

In questo esempio, creeremo una macro semplice che inserisce la stringa specificata al posto del contenuto di destinazione specificato nel modello elaborato. Quindi la utilizzeremo per inserire un campo WaitHandleCondition vuoto nella posizione specificata nel modello elaborato.

Creazione di una macro

Prima di utilizzare una macro, dobbiamo completare due operazioni: creare la funzione Lambda che esegue l’elaborazione del modello desiderato e quindi rendere quella funzione Lambda disponibile per CloudFormation creando una definizione di macro.

L’esempio di modello seguente contiene la definizione del nostro esempio di macro. Per rendere la macro disponibile in un determinato Account AWS, crea uno stack a partire dal modello. La definizione di macro specifica il nome di macro, una breve descrizione, e fa riferimento all’ARN della funzione Lambda che CloudFormation richiama quando questa macro viene utilizzata in un modello (non abbiamo incluso una proprietà LogGroupName o LogRoleARN per la registrazione degli errori).

In questo esempio, supponiamo che lo stack creato da questo modello sia denominato JavaMacroFunc. Poiché la proprietà Name della macro viene impostata sul nome dello stack, anche la macro risultante è denominata 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'

Utilizzo della macro

In questo caso, per utilizzare la nostra macro, la includeremo in un modello utilizzando la funzione intrinseca Fn::Transform.

Quando creiamo uno stack utilizzando il modello seguente, CloudFormation chiama il nostro esempio di macro. La funzione Lambda sottostante sostituisce una stringa specificata con un’altra stringa specificata. In questo caso, il risultato è una risorsa AWS::CloudFormation::WaitConditionHandle vuota che viene inserita nel modello elaborato.

Parameters: ExampleParameter: Type: String Default: 'SampleMacro' Resources: 2a: Fn::Transform: Name: "JavaMacroFunc" Parameters: replacement: 'AWS::CloudFormation::WaitConditionHandle' target: '$$REPLACEMENT$$' Type: '$$REPLACEMENT$$'
  • La macro da invocare è specificata come JavaMacroFunc, ovvero la macro dell’esempio di definizione di macro precedente.

  • Alla macro vengono passati due parametri, target e replacement, che rappresentano la stringa di destinazione e il valore di sostituzione desiderato.

  • La macro può funzionare sul contenuto del nodo Type in quanto Type è un elemento di pari livello della funzione Fn::Transform che fa riferimento alla macro.

  • La risorsa AWS::CloudFormation::WaitConditionHandle risultante è denominata 2a.

  • Il modello contiene inoltre un parametro di modello, ExampleParameter, a cui la macro ha accesso (ma che non utilizza in questo caso).

Dati di input Lambda

Quando CloudFormation elabora il nostro esempio di modello durante la creazione dello stack, passa la mappatura di eventi seguente alla funzione Lambda a cui si fa riferimento nella definizione di 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 codice JSON che rappresenta il frammento di modello che la macro può elaborare. Questo frammento comprende elementi di pari livello della chiamata di funzione Fn::Transform, ma non la chiamata stessa. Inoltre, params contiene codice JSON che rappresenta i parametri di macro. In questo caso, sostituzione e destinazione. Analogamente, templateParameterValues contiene codice JSON che rappresenta i parametri specificati per il modello nel complesso.

Codice della funzione Lambda

Di seguito è riportato il codice effettivo della funzione Lambda sottostante l’esempio di macro JavaMacroFunc. L’esempio esegue l’iterazione del frammento di modello incluso nella risposta (che sia in un formato di stringa, elenco o mappa), alla ricerca della stringa di destinazione specificata. Se trova la stringa di destinazione specificata, la funzione Lambda sostituisce la stringa di destinazione con la stringa di sostituzione specificata. In caso contrario, la funzione lascia il frammento di modello invariato. La funzione restituisce quindi a CloudFormation una mappa delle proprietà previste, descritte in dettaglio di seguito.

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

Risposta della funzione Lambda

Di seguito è riportata la mappatura che la funzione Lambda restituisce a CloudFormation per l’elaborazione.

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

  • status : SUCCESS

  • fragment :

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

requestId corrisponde a quello inviato da CloudFormation e il valore status di SUCCESS denota che la funzione Lambda ha elaborato senza errori il frammento di modello incluso nella richiesta. In questa risposta, fragment contiene codice JSON che rappresenta il contenuto da inserire nel modello elaborato al posto del frammento del modello originale.

Modello elaborato risultante

Dopo che CloudFormation riceve una risposta di esito positiva dalla funzione Lambda, inserisce il frammento di modello restituito nel modello elaborato.

Di seguito è riportato il modello elaborato risultante per il nostro esempio. La chiamata della funzione intrinseca Fn::Transform che faceva riferimento alla macro JavaMacroFunc non è più inclusa. Il frammento di modello restituito dalla funzione Lambda è incluso nella posizione appropriata, con il risultato che il contenuto "Type": "$$REPLACEMENT$$" è stato sostituito con "Type": "AWS::CloudFormation::WaitConditionHandle".

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