How do I update the AWS CloudFormation cfn-response module for AWS Lambda functions that run on Python 3.7?

5 minute read
0

I want to update the AWS CloudFormation cfn-response module for AWS Lambda functions that run on Python 3.7.

Resolution

The following steps are applicable only to Lambda functions that run on Python 3.7. The following commands apply for both Linux and macOS environments. Syntax might vary on PowerShell.

Note: If you receive errors when you run AWS Command Line Interface (AWS CLI) commands, then see Troubleshoot AWS CLI errors. Also, make sure that you're using the most recent AWS CLI version.

To update the cfn-response module, complete the following steps:

  1. To find the stacks that contain custom resources, run the list-stacks following command:

    aws cloudformation list-stacks --region us-east-1 | grep -oE 'arn:[^"]+' | while read arn; do aws cloudformation list-stack-resources --stack-name $arn --region us-east-1 | grep -E '(Custom::)|(::CustomResource)' | awk '{print $2}' | while read resource; do if [[ -n $resource ]]; then echo $arn; echo $resource; fi; done; done

    You should see output similar to the following example output:

    arn:aws:cloudformation:us-east-1:123456789012:stack/TestStack/3497b950-55f1-11eb-aad4-124a026c8667
    "ResourceType": "AWS::CloudFormation::CustomResource",
  2. To find the Lambda function associated with the custom resource, run the get-template command. The command checks the custom resource's ServiceToken property from the stack's template:

    aws cloudformation get-template --stack-name TestStack | jq -r .TemplateBody

    Note: The command in step 2 previews the stack's template by using the jq option (from the jq website) to format the response.

    Example output:

    Resources:
      MyCustomResource:
        Type: AWS::CloudFormation::CustomResource
        Properties:
          ServiceToken: !GetAtt MyFunction.Arn
          Name: "John"
      MyFunction:
        Type: AWS::Lambda::Function
        Properties:
          Handler: index.handler
          Role: !GetAtt MyRole.Arn
          Runtime: python3.7
          Code:
            ZipFile: |
              import cfnresponse
              def handler(event, context):
                responseData = {'Message': 'Hello {}!'.format(event['ResourceProperties']['Name'])}
                cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, "CustomResourcePhysicalID")
      MyRole:
        Type: AWS::IAM::Role
        Properties:
          AssumeRolePolicyDocument:
            Statement:
              - Effect: Allow
                Principal:
                  Service:
                    - lambda.amazonaws.com
                Action:
                  - sts:AssumeRole
          ManagedPolicyArns:
            - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
    Outputs:
      Result:
        Value: !GetAtt MyCustomResource.Message

    Note: The output for step 2 is an example of a minimal template for a Lambda-backed custom resource. The property ServiceToken: !GetAtt MyFunction.Arn is in the MyCustomResource section. The value for !GetAtt MyFunction.Arn is either the Amazon Resource Name (ARN) of the Amazon Simple Notification Service (Amazon SNS) topic or the Lambda function.

  3. From the command output template in step 2, identify where your Lambda function is defined. If your Lambda function is in the same stack as the custom resource, skip to step 4. For example, the Fn::GetAtt function in step 2 shows that the Lambda function is defined in the same template as the custom resource.

    If the ServiceToken property points to a hardcoded ARN, then the Lambda function could be in another stack. If the ServiceToken property is resolved through Fn::Import, then use the list-exports API in AWS CloudFormation to look up the value. For example:

    aws cloudformation list-exports --region us-east-1
    {
        "Exports": [
            {
                "ExportingStackId": "arn:aws:cloudformation:us-east-1:123456789012:stack/SomeOtherStack/481dc040-b283-11e9-b1bd-12d607a4fd1c",
                "Value": "arn:aws:lambda:us-east-1:123456789012:function:SomeOtherStack-MyFunction-5ZE2CQO8RAA9",
                "Name": "MyExport"
            }
        ]
    }

    Use the function list-tags to check for function tags located in a separate stack in order to identify the AWS CloudFormation stack ARN. For example:

    aws lambda list-tags --resource arn:aws:lambda:us-east-1:123456789012:function:TestStack-MyFunction-5ZE2CQO8RAA9 | grep stack-id

    You receive output similar to the following:

    "aws:cloudformation:stack-id": "arn:aws:cloudformation:us-east-1:123456789012:stack/TestStack/3497b950-55f1-11eb-aad4-124a026c8667"

    Note: You can also find function tags in the AWS Lambda console.

  4. To allow AWS CloudFormation to load the latest cfn-response module in your Lambda function, update the inline source code of your Lambda function. For example:

    Code:
            ZipFile: |
              import cfnresponse
              def handler(event, context):
                responseData = {'Message': 'Hello {}!'.format(event['ResourceProperties']['Name'])}
                cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, "CustomResourcePhysicalID")

    Note: See step 2 for an example template that has a Lambda function with inline source code.

    The following cfn-response module code example is loaded by AWS CloudFormation into your Lambda function. For example:

    from botocore.vendored import requests
    import json
     
    SUCCESS = "SUCCESS"
    FAILED = "FAILED"
     
    def send(event, context, responseStatus, responseData, physicalResourceId=None, noEcho=False):
        responseUrl = event['ResponseURL']
     
        print(responseUrl)
     
        responseBody = {}
        responseBody['Status'] = responseStatus
        responseBody['Reason'] = 'See the details in CloudWatch Log Stream: ' + context.log_stream_name
        responseBody['PhysicalResourceId'] = physicalResourceId or context.log_stream_name
        responseBody['StackId'] = event['StackId']
        responseBody['RequestId'] = event['RequestId']
        responseBody['LogicalResourceId'] = event['LogicalResourceId']
        responseBody['NoEcho'] = noEcho
        responseBody['Data'] = responseData

    Note: For more information, see the code examples in the "Module source code" section of the cfn-response module.

    The cfn-response module code example uses botocore.requests in the Lambda function's deployment package.

    To update the cfn-response module to the latest version that uses urllib3, update the function's inline code in the AWS CloudFormation template. Do this by adding a comment to the inline Lambda function's code. For example:

    ZipFile: |
               import cfnresponse
               def handler(event, context):
    +            # This comment was added to force an update on this function's code
                 responseData = {'Message': 'Hello {}!'.format(event['ResourceProperties']['Name'])}
                 cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, "CustomResourcePhysicalID")
       MyRole:
  5. Save any changes to the template that contains your Lambda function.

  6. Update your stack. The cfn-response module is modified after the stack is finished updating.

    Note: If your function's code resides in an Amazon Simple Storage Service (Amazon S3) bucket or Amazon Elastic Container Registry (Amazon ECR) image, then you must update the module to include the version with urllib3. To get the source code for the latest version of the cfn-response module, see cfn-response module.

    Note: If a new Python or JavaScript runtime introduces a breaking change, you must update the cfn-response module. Instead of updating the ZipFile again, you can automatically attach the newest version of the cfn-response module whenever a function's Runtime property is updated.

AWS OFFICIAL
AWS OFFICIALUpdated 2 months ago