AWS Cloudformation-如何在 json/yaml 模板中处理字符串大写或小写

AWS Cloudformation- How to do string Uppercase or lowercase in json/yaml template

我正在使用 AWS CloudFormation,我创建了一个模板,我在其中要求用户 select 环境。

根据 selected 值我创建了资源。用户必须在 DEV、QA、PROD、UAT 等之间使用 select,但是当我将此值作为 S3 存储桶名称的后缀 (-downloads.com) 时,它是不允许的,因为 S3 存储桶名称中不允许使用大写字母。

所以我在 JSON 中进行了更改,其中我使用 fn::Transform"Condition":"Lower" 但随后在创建以下资源时发生错误。

No transform named 871247504605::String found.. Rollback requested by user.

下面是我的 CloudFormation JSON

{
    "AWSTemplateFormatVersion": "2010-09-09",
    "Description": "Provides nesting for required stacks to deploy a full resource of ****",
    "Metadata": {
        "AWS::CloudFormation::Interface": {
            "ParameterGroups": [
                {
                    "Label": {
                        "default": "Enviroment Selection"
                    },
                    "Parameters": [
                        "selectedEnv"
                    ]
                }
            ],
            "ParameterLabels": {
                "selectedEnv": {
                    "default": "Please select Enviroment"
                }
            }
        }
    },
    "Parameters": {
        "selectedEnv": {
            "Type": "String",
            "Default": "DEV",
            "AllowedValues": [
                "DEV",
                "QA",
                "UAT",
                "PROD"
            ]
        }
    },
    "Resources": {
        "S3BucketName": {
            "Type": "AWS::S3::Bucket",
            "Properties": {
                "BucketName": {
                    "Fn::Join": [
                        "",
                        [
                            {
                                "Fn::Transform": {
                                    "Name": "MyString",
                                    "Parameters": {
                                        "InputString": {
                                            "Ref": "selectedEnv"
                                        },
                                        "Operation": "Lower"
                                    }
                                }
                            },
                            "-deployment.companyname.com"
                        ]
                    ]
                },
                "PublicAccessBlockConfiguration": {
                    "BlockPublicAcls": "true",
                    "BlockPublicPolicy": "true",
                    "IgnorePublicAcls": "true",
                    "RestrictPublicBuckets": "true"
                },
                "Tags": [
                    {
                        "Key": "ENV",
                        "Value": {
                            "Ref": "selectedEnv"
                        }
                    },
                    {
                        "Key": "Name",
                        "Value": {
                            "Fn::Join": [
                                "",
                                [
                                    {
                                        "Ref": "selectedEnv"
                                    },
                                    "deployments"
                                ]
                            ]
                        }
                    }
                ]
            },
            "Metadata": {
                "AWS::CloudFormation::Designer": {
                    "id": "c81705e6-6c88-4a3d-bc49-80d8736bd88e"
                }
            }
        },
        "QueueForIOT": {
            "Type": "AWS::SQS::Queue",
            "Properties": {
                "QueueName": {
                    "Fn::Join": [
                        "",
                        [
                            {
                                "Ref": "selectedEnv"
                            },
                            "QueueForIOT"
                        ]
                    ]
                },
                "DelaySeconds": "0",
                "MaximumMessageSize": "262144",
                "MessageRetentionPeriod": "345600",
                "ReceiveMessageWaitTimeSeconds": "20",
                "VisibilityTimeout": "30"
            },
            "Metadata": {
                "AWS::CloudFormation::Designer": {
                    "id": "6484fbb7-a188-4a57-a40e-ba9bd69d4597"
                }
            }
        }
    },
    "Outputs": {
        "Help": {
            "Description": "This is description",
            "Value": ""
        }
    }
}

我的问题是,我想为 S3 存储桶或任何其他资源设置小写或有时大写的值。如何做到这一点?

附上模板创建错误的图片。

我得到了这个问题的答案。 为此,我使用了 Mappings JSON,其中我添加了一些值,例如 If Selected value is DEV then use dev, If QA then qa like this, and used below JSON which used Fn :FindInMap

[ { "Fn::FindInMap": [ "Enviroment", "PlatformName", { "Ref": "selectedEnv" } ] }, "clientname" ]

下面是映射 JSON:

"Mappings" : { "Enviroment" : { "PlatformName" : { "DEV" : "dev", "QA" : "qa", "UAT" : "uat", "PROD" : "prod" } } }

您可以使用 CloudFormation 宏执行此操作。

Parameters:
  InputString:
    Default: "This is a test input string"
    Type: String
Resources:
  S3Bucket:
    Type: "AWS::S3::Bucket"
    Properties:
      Tags:
        - Key: Upper
          Value:
            'Fn::Transform':
             - Name: 'String'
               Parameters:
                 InputString: !Ref InputString
                 Operation: Upper

https://github.com/awslabs/aws-cloudformation-templates/tree/master/aws/services/CloudFormation/MacrosExamples/StringFunctions

Below is from the AWS Documentation


AWS CloudFormation 宏的工作原理

使用宏处理模板有两个主要步骤:创建宏本身,然后使用宏对模板执行处理。

要创建宏定义,您需要创建以下内容:

  • 执行模板处理的 AWS Lambda 函数。此 Lambda 函数接受片段或整个模板,以及您定义的任何其他参数。它 returns 已处理的模板片段或整个模板作为响应。

  • AWS::CloudFormation::Macro 类型的资源,使用户能够从 AWS CloudFormation 模板中调用 Lambda 函数。此资源指定要为此宏调用的 Lambda 函数的 ARN,以及用于协助调试的其他可选属性。要在帐户中创建此资源,请创建一个包含 AWS::CloudFormation::Macro 资源的堆栈模板,然后从该模板创建一个堆栈。

要使用宏,请在模板中引用宏:

  • 要处理模板的一部分或片段,请在相对于要转换的模板内容定位的 Fn::Transform 函数中引用宏。当使用Fn::Transform时,您还可以传递它需要的任何指定参数。

  • 要处理整个模板,请在模板的转换部分引用宏。

接下来,您通常会创建一个更改集,然后执行它。 (处理宏可以添加您可能不知道的多个资源。为确保您了解宏引入的所有更改,我们强烈建议您使用更改集。)AWS CloudFormation 传递指定的模板内容,以及与任何额外的指定参数,到宏资源中指定的 Lambda 函数。 Lambda 函数 returns 处理后的模板内容,无论是片段还是整个模板。

调用模板中的所有宏后,AWS CloudFormation 会生成一个包含已处理模板内容的更改集。查看更改集后,执行它以应用更改。


例如:

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

先生Young 是正确的,这是您需要用来调用宏的语法。

但是,他们和文档都没有提到的关键因素是,为了调用转换宏,您需要先将此堆栈部署到您的帐户中,然后才能使用自述文件中列出的功能。

https://github.com/awslabs/aws-cloudformation-templates/blob/master/aws/services/CloudFormation/MacrosExamples/StringFunctions/string.yaml

我认为文档可以在这方面进行澄清,我会看看是否可以 PR 进行澄清

接受的答案建议使用 CloudFormation 宏,另一个答案建议使用 FindInMap

FindInMap 在这里不是很有用,因为它只适用于硬编码值。

宏建议可行,但需要大量设置(在单独的堆栈中声明宏,确保您的部署者角色有权调用 Lambda,并且您的 CloudFormation 堆栈是使用 CAPABILITY_AUTO_EXPAND 部署的, 等等)。

在模板中声明自定义资源是可行的,IMO 比依赖宏涉及的工作更少。这是一个 CFN 片段,它调整了您询问的 S3 存储桶资源,演示了自定义资源的使用,该资源将小写任意 S3 存储桶名称:

  # Custom resource to transform input to lowercase.                                             
  LowerCaseLambda:
    Type: 'AWS::Lambda::Function'
    Properties:
      Description: Returns the lowercase version of a string
      MemorySize: 256
      Runtime: python3.8
      Handler: index.lambda_handler
      Role: !GetAtt LowerCaseLambdaRole.Arn
      Timeout: 30
      Code:
        ZipFile: |
          import cfnresponse

          def lambda_handler(event, context):                                                    
              output = event['ResourceProperties'].get('InputString', '').lower()                
              responseData = {'OutputString': output}                                            
              cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)                

  LowerCaseLambdaRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service:
                - "lambda.amazonaws.com"
            Action:
              - "sts:AssumeRole"
      Policies:
        - PolicyName: "lambda-write-logs"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action:
                  - "logs:CreateLogGroup"
                  - "logs:CreateLogStream"
                  - "logs:PutLogEvents"
                Resource: "arn:aws:logs:*:*"


  S3BucketName:
    Type: Custom::Lowercase
    Properties:
      ServiceToken: !GetAtt LowerCaseLambda.Arn
      InputString: !Ref selectedEnv

  S3Bucket:
    BucketName: !Join
      - ''
      - - !GetAtt S3BucketName.OutputString
        - "-deployment.companyname.com"