シンプルな文字列置換マクロの例 - AWS CloudFormation

シンプルな文字列置換マクロの例

次の例では、マクロを使用するプロセスとして、テンプレートでのマクロの定義、マクロ用の Lambda 関数の作成、テンプレートでのマクロの使用について説明します。

この例では、処理されたテンプレートの指定されたターゲットコンテンツの代わりに指定された文字列を挿入する簡単なマクロを作成します。次に、それを使用して、処理されたテンプレート内の指定された場所に空白の WaitHandleCondition を挿入します。

マクロの作成

マクロを使用する前に、次の 2 つのことを完了する必要があります。目的のテンプレート処理を実行する Lambda 関数を作成してからマクロ定義を作成し、その Lambda 関数を CloudFormation で使用できるようにすることです。

次のサンプルテンプレートには、サンプルマクロの定義が含まれています。特定の AWS アカウント でマクロを利用できるようにするために、テンプレートからスタックを作成します。マクロ定義は、マクロ名、簡単な説明を指定し、このマクロがテンプレートで使用されたときに CloudFormation が呼び出す Lambda 関数の ARN を参照します。(エラーログ用の LogGroupName または LogRoleARN プロパティは含めていません。)

この例では、このテンプレートから作成されたスタックの名前が JavaMacroFunc であるとします。マクロの Name プロパティはスタック名に設定されているので、結果として得られるマクロも 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'

マクロの使用

マクロを使用するには、Fn::Transform 組み込み関数を使用してそれをテンプレートに含めます。

以下のテンプレートを使用してスタックを作成すると、CloudFormation はサンプルマクロを呼び出します。基盤となる Lambda 関数は、指定された文字列を別の指定された文字列で置き換えます。この場合、結果は空白になり AWS::CloudFormation::WaitConditionHandle が処理済みテンプレートに挿入されます。

Parameters: ExampleParameter: Type: String Default: 'SampleMacro' Resources: 2a: Fn::Transform: Name: "JavaMacroFunc" Parameters: replacement: 'AWS::CloudFormation::WaitConditionHandle' target: '$$REPLACEMENT$$' Type: '$$REPLACEMENT$$'
  • 呼び出すマクロは、前のマクロ定義の例の JavaMacroFunc として指定されています。

  • このマクロには、targetreplacement の 2 つのパラメータが渡されます。これらは、ターゲット文字列とその目的の置換値を表します。

  • Type はマクロを参照する Fn::Transform 関数の兄弟であるため、マクロは Type ノードの内容を操作できます。

  • 結果の AWS::CloudFormation::WaitConditionHandle2a という名前になります。

  • テンプレートには、テンプレートパラメータ ExampleParameter も含まれています。このパラメータには、マクロからもアクセスできます (ただし、この場合は使用しません)。

Lambda 入力データ

CloudFormation がスタックの作成中にサンプルのテンプレートを処理するときに、JavaMacroFunc マクロ定義で参照されている Lambda 関数に次のイベントマッピングを渡します。

  • 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 には、マクロが処理できるテンプレートフラグメントを表す JSON が含まれています。このフラグメントは Fn::Transform 関数呼び出しの兄弟で構成されていますが、関数呼び出しそのものではありません。また、params にはマクロパラメータを表す JSON が含まれています。この場合は、置き換えとターゲットです 同様に、templateParameterValues には、テンプレート全体として指定されたパラメータを表す JSON が含まれています。

Lambda 関数コード

以下は、JavaMacroFunc マクロの基礎となる Lambda 関数の実際のコードです。指定されたターゲット文字列を探しながら、応答に含まれるテンプレートフラグメント (文字列、リスト、マップ形式など) を繰り返し処理します。指定されたターゲット文字列が見つかった場合、Lambda 関数はターゲット文字列を指定された置換文字列で置き換えます。そうでない場合は、関数はテンプレートフラグメントを変更しないままにします。それから、関数は 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; } }

Lambda 関数レスポンス

以下は、Lambda 関数が処理のために CloudFormation に返すマッピングです。

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

  • status : SUCCESS

  • fragment :

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

requestId は CloudFormation から送信されたものと一致し、statusSUCCESS は Lambda 関数がリクエストに含まれるテンプレートフラグメントを正常に処理したことを示します。このレスポンスでは、fragment は元のテンプレートスニペットの代わりに処理されたテンプレートに挿入するコンテンツを表す JSON を含んでいます。

結果として得られる処理済みテンプレート

CloudFormation は、Lambda 関数から正常な応答を受け取ると、返されたテンプレートフラグメントを処理済みテンプレートに挿入します。

以下は、この例で処理されたテンプレートです。JavaMacroFunc マクロを参照した Fn::Transform 組み込み関数呼び出しは含まれなくなりました。Lambda 関数によって返されたテンプレートフラグメントは適切な場所に含まれ、その結果、"Type": "$$REPLACEMENT$$""Type": "AWS::CloudFormation::WaitConditionHandle" に置き換えられています。

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