CloudFormationで文字列の大文字/小文字を変換する

2022.01.15

いわさです。

先日CloudFormationで別のリソースから取得した文字列値を使ってVPCエンドポイントを作成しようとしたところ、エラーになりました。

The Vpc Endpoint Service 'com.amazonaws.ap-northeast-1.managedblockchain.n-clxlsessenfatn2ty2tiehnf6m' does not exist

どうやら大文字と小文字の違いでサービスが見つからないことで失敗しているようです。

CloudFormationで大文字を小文字に変換出来ないものかと探したところTransformを使えば出来るようなので本日は試してみました。

Fn::Transform

Fn::Transformについては以下の記事で文字列のUPPER/LOWER含めて少し触れられています。

Transformで利用するにあたって関数の作成が必要なのですが、awslabsのサンプルリポジトリで紹介されておりそのまま利用出来ます。
いつも思うのですが、なんでもありますね、このリポジトリ群。

以下のStackを自分のAWSアカウントで用意しておくと好きな時に今後使えるので便利ですね。

AWSTemplateFormatVersion: 2010-09-09
Resources:
  TransformExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: [lambda.amazonaws.com]
            Action: ['sts:AssumeRole']
      Path: /
      Policies:
        - PolicyName: root
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: ['logs:*']
                Resource: 'arn:aws:logs:*:*:*'
  TransformFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
          import traceback
          def handler(event, context):
              response = {
                  "requestId": event["requestId"],
                  "status": "success"
              }
              try:
                  operation = event["params"]["Operation"]
                  input = event["params"]["InputString"]
                  no_param_string_funcs = ["Upper", "Lower", "Capitalize", "Title", "SwapCase"]
                  if operation in no_param_string_funcs:
                      response["fragment"] = getattr(input, operation.lower())()
                  elif operation == "Strip":
                      chars = None
                      if "Chars" in event["params"]:
                          chars = event["params"]["Chars"]
                      response["fragment"] = input.strip(chars)
                  elif operation == "Replace":
                      old = event["params"]["Old"]
                      new = event["params"]["New"]
                      response["fragment"] = input.replace(old, new)
                  elif operation == "MaxLength":
                      length = int(event["params"]["Length"])
                      if len(input) <= length:
                          response["fragment"] = input
                      elif "StripFrom" in event["params"]:
                          if event["params"]["StripFrom"] == "Left":
                              response["fragment"] = input[len(input)-length:]
                          elif event["params"]["StripFrom"] != "Right":
                              response["status"] = "failure"
                      else:
                          response["fragment"] = input[:length]
                  else:
                      response["status"] = "failure"
              except Exception as e:
                  traceback.print_exc()
                  response["status"] = "failure"
                  response["errorMessage"] = str(e)
              return response
      Handler: index.handler
      Runtime: python3.6
      Role: !GetAtt TransformExecutionRole.Arn
  TransformFunctionPermissions:
    Type: AWS::Lambda::Permission
    Properties:
      Action: 'lambda:InvokeFunction'
      FunctionName: !GetAtt TransformFunction.Arn
      Principal: 'cloudformation.amazonaws.com'
  Transform:
    Type: AWS::CloudFormation::Macro
    Properties:
      Name: 'String'
      Description: Provides various string processing functions
      FunctionName: !GetAtt TransformFunction.Arn

利用側は以下のようになります。

...

Outputs:
  NetworkId:
    Value: !GetAtt NetworkAndInitialMember.NetworkId
  NetworkIdLower:
    Value: 
      Fn::Transform:
        - Name: String
          Parameters:
            InputString: n-CLXLSES5ENFATN2TY2TIEHNF6M
...

注意

注意点というかTransformの仕様なのですが、リソースの動的なプロパティを渡すことは出来ません。

Failed to digest functions within transform parameters, intrinsic functions in transform block must only contain parameter values or stack metadata.

...

Outputs:
  NetworkId:
    Value: !GetAtt NetworkAndInitialMember.NetworkId
  NetworkIdLower:
    Value: 
      Fn::Transform:
        - Name: String
          Parameters:
            InputString: !GetAtt NetworkAndInitialMember.NetworkId

...

なので、本来やりたかった上記のような使い方は出来ません。
結局カスタムリソースで対応することにしました。

ただし、スタックパラメータであれば利用できるので、ネストさせれば利用できそうです。

さいごに

本日はTransformを使って、文字列の大文字/小文字を変換してみました。

小文字変換くらいであれば組み込みで出来るだろうと思っていたらLambda関数を用意しなければならないなど思ったより準備が必要でした。
しかし、AWSのレポジトリで変換関数サンプルが提供されているのはありがたいですね。
今回の仕組みは他にも色々と応用が効きそうなので、カスタムリソースと併せて何でも出来そうですね。複雑にならないようには努めたいものですが。