AWS CloudFormation event hanging in CREATE_IN_PROGRESS state after custom resource (lambda) invocation

Viewed 4382

I have a lambda that has a python script to create an application.properties file in parameter store. I have a cloudformation template that invokes this lambda to create application.properties. My cloudformation template looks like:

{
   "Description": "Create SSM Parameter",
   "Resources": {
      "primerinvoke": {
         "Type": "AWS::CloudFormation::CustomResource",
         "Properties": {
            "Handler": "lambda_function.lambda_handler",
            "ServiceToken": "arn:aws:lambda:us-east-1:1234:function:test_lambda",
            "FunctionName": "test_lambda"
         }
      }
   }
}

My python script to create SSM parameter (whose path is: /myapp/dev/test/application.properties) is:

import boto3
import os

region = os.environ['AWS_REGION']
client = boto3.client('ssm')
def ssm_create():
    response = client.put_parameter(Name='/myapp/'
                                    + os.environ['environment']
                                    + '/test/application.properties',
                                    Description='string',
                                    Value='APPLICATION_NAME=myapp',
                                    Type='SecureString', Overwrite=True)
    return response
def lambda_handler(event, context):
    PutParameterResult = ssm_create()

I used import cfnresponse as suggested in AWS lambda: No module named 'cfnresponse', however, the import doesn't work and I don't know how to install cfnresponse library externally on the lambda.

The code runs successfully which means that cloudformation template was able to invoke the lambda & it's script and create the SSM parameter but it hangs after ssm_create() function in my script completes. I don't know how to return a status of "SUCCESS" from my script to the cloudformation stack to prevent it from hanging in CREATE_IN_PROGRESS state.

Any help will be greatly appreciated! Thanks.

EDIT1: I updated my code to:

responseStatus = 'SUCCESS'
responseBody={}
def sendResponse(event, context, responseStatus):
            responseBody = {'Status': responseStatus,
                            'Reason': 'See the details in CloudWatch Log Stream: ' + context.log_stream_name,
                            'PhysicalResourceId': context.log_stream_name,
                            'StackId': event['StackId'],
                            'RequestId': event['RequestId'],
                            'LogicalResourceId': event['LogicalResourceId'],
                            }
            print 'RESPONSE BODY:n' + json.dumps(responseBody)
def lambda_handler(event, context):
                logger.info(event)
                test_ssm_create()
                try:
                    req = requests.put(event['ResponseURL'], data=json.dumps(sendResponse(event, context, responseStatus)))
                    if req.status_code != 200:
                        print req.text
                        raise Exception('Recieved non 200 response while sending response to CFN.')
                except requests.exceptions.RequestException as e:
                    print e
                    raise
                return
                print("COMPLETE")

The req.status_code is giving 200 but the cloudformation stack is again getting stuck at CREATE_IN_PROGRESS. Still not sure how to make this work.

1 Answers

As noted in the CloudFormation documentation, CloudFormation expects your Lambda function to callback to it once it has completed its operation; CloudFormation will pause execution until this callback is received. The event sent to your Lambda function by CloudFormation contains the callback URL (ResponseURL) as shown in this example:

{
   "RequestType" : "Create",
   "ResponseURL" : "http://pre-signed-S3-url-for-response",
   "StackId" : "arn:aws:cloudformation:us-west-2:123456789012:stack/stack-name/guid",
   "RequestId" : "unique id for this create request",
   "ResourceType" : "Custom::TestResource",
   "LogicalResourceId" : "MyTestResource",
   "ResourceProperties" : {
      "Name" : "Value",
      "List" : [ "1", "2", "3" ]
   }
}

If you are not able to import the cfnresponse module, which seems to be python2 code, you should be able to simulate the same with python3. Try adding something like this code in your lambda_handler function. Note that you there are required keys in the response that are detailed in this AWS documentation page (i.e. PhysicalResourceId, StackId, RequestId, LogicalResourceId that are straightforward to add, see the documentation).

import requests
import json
import uuid

status = {'Status': 'SUCCESS',
          'PhysicalResourceId': 'MyResource_' + str(uuid.uuid1()),
          'StackId': event['StackId'],
          'RequestId': event['RequestId'],
          'LogicalResourceId': event['LogicalResourceId']
         }
r = requests.put(event['ResponseURL'], data=json.dumps(status))

So to edit the code in your question:

responseStatus = 'SUCCESS'

def getResponse(event, context, responseStatus):
            responseBody = {'Status': responseStatus,
                            'PhysicalResourceId': context.log_stream_name,
                            'StackId': event['StackId'],
                            'RequestId': event['RequestId'],
                            'LogicalResourceId': event['LogicalResourceId'],
                            }
            responseBody = json.dumps(responseBody)
            print 'RESPONSE BODY:n' + responseBody

            return responseBody

def lambda_handler(event, context):
                logger.info(event)
                test_ssm_create()
                try:
                    req = requests.put(event['ResponseURL'], data=getResponse(event, context, responseStatus))
                    if req.status_code != 200:
                        print req.text
                        raise Exception('Received non 200 response while sending response to CFN.')
                except requests.exceptions.RequestException as e:
                    print e
                    raise
                return
                print("COMPLETE")
Related