【问题标题】:AWS Cloudformation- How to do string Uppercase or lowercase in json/yaml templateAWS Cloudformation-如何在 json/yaml 模板中处理字符串大写或小写
【发布时间】:2020-04-23 20:01:48
【问题描述】:

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

基于选定的价值,我创建了资源。用户必须在 DEV、QA、PROD、UAT 等之间进行选择,但是当我将此值添加到 S3 存储桶名称 (-downloads.com) 后,它是不允许的,因为 S3 存储桶名称中不允许使用大写字母。

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

找不到名为 871247504605::String 的转换。用户请求回滚。

下面是我的 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 存储桶或任何其他资源使用小写或有时大写的值。如何做到这一点?

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

【问题讨论】:

  • JSON 无效,能否分享完整的 cloudformatino 模板?
  • 我也更新了问题和 JSON。请帮帮我。谢谢
  • 从您的问题中不清楚您是否有实现 MyString 宏的 lambda 函数,这是 aws 为创建自定义转换所必需的。更多信息可以找到here。示例(包括 lambda 函数)可以在 here 找到。
  • 您可以将用户输入的小写,即 DEV=dev
  • 您不能仅使用 json/yaml 进行此转换。虽然您只能使用 json/yaml 做的就是通过将 mappingsFn::FindInMap 合并到您的模板中来映射 DEV to dev, PROD to prod, etc

标签: json amazon-web-services amazon-s3 amazon-cloudformation aws-cloudformation-custom-resource


【解决方案1】:

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

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

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

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

【讨论】:

    【解决方案2】:

    我得到了这个问题的答案。 为此,我使用了 Mappings JSON,其中我添加了诸如 If Selected value is DEV then use dev, If QA then qa like this, 并在使用 Fn:FindInMap

    的 JSON 下面使用

    [ { “Fn::FindInMap”:[ “环境”, "平台名称", { “参考”:“选定环境” } ] }, “客户名称” ]

    以下是映射 JSON:

    “映射”:{ “环境”:{ “平台名称”:{ “开发”:“开发”, “质量保证”:“质量保证”, “UAT”:“UAT”, “产品”:“产品” } } }

    【讨论】:

    • 我也这样做过,但真的希望他们能证明我们可以用于这些简单操作的函数。在发送到 CloudFormation 之前,我一直在 Jenkins Shell 中进行这些类型的操作。外壳示例:UpperVar=DEV, LowerVar=${UpperVar,,}
    【解决方案3】:

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

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

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

    在模板中声明自定义资源将起作用,并且 IMO 所涉及的工作量比依赖宏要少。这是一个 CFN sn-p,改编了您询问的 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"
    

    【讨论】:

      【解决方案4】:

      您可以使用 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 函数接受 sn-p 或整个模板,以及您定义的任何其他参数。它返回已处理的模板 sn-p 或整个模板作为响应。

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

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

      • 要处理模板的某个部分或 sn-p,请在 Fn::Transform 函数中引用宏,该函数位于与要转换的模板内容相关的位置。使用 Fn::Transform 时,您还可以传递它需要的任何指定参数。

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

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

      调用模板中的所有宏后,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
      

      【讨论】:

      • 从发布的 README 中并不清楚,但是您需要将该 Python 文件添加为转换函数,而不仅仅是使用上层函数。 CF 对此没有任何内置支持,您需要添加自己的转换函数才能做到这一点。
      • @shanet 这是一个 cloudformation 宏,您将部署为 lambda。可以在此处找到有关 CloudFormation 宏的更多信息。 docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/…
      • 是的,这就是我想说的。上面的答案和来自 github 链接的 README 看起来像这样的语法,当您实际需要将链接示例中的 Python 代码添加为宏/lambda/您有什么以便使用时,该语法只能用于 CF 新手它。
      • 我不确定您是如何得出这个结论的。文档明确指出“使用宏处理模板有两个主要步骤:创建宏本身,然后使用宏对模板执行处理。” ... “要创建宏定义,您需要创建以下内容:” ... “用于执行模板处理的 AWS Lambda 函数。” ...“AWS::CloudFormation::Macro 类型的资源,使用户能够从 AWS CloudFormation 模板中调用 Lambda 函数。”
      • 哪些文档?您的答案或指向 GitHub 存储库的链接中没有任何内容表明“使用宏处理模板有两个主要步骤......”。对于 CloudFormation 的新手,我会假设“宏”是内置的,我可以在模板中使用。不是这样的,我需要先自己定义宏。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-04-03
      • 2010-11-04
      • 1970-01-01
      • 2013-07-07
      • 1970-01-01
      相关资源
      最近更新 更多