Search code examples
python-3.xaws-lambdaaws-cloudformationaws-event-bridgeaws-cloudformation-custom-resource

Custom Resource CloudFormation to tag Event Rules


I am trying to create custom resource in CloudFormation to tag Event Rule. This is the lambda:

from json import dumps
import sys
import traceback
import urllib.request
import boto3


def send_response(event, context, response):
    """Send a response to CloudFormation to handle the custom resource lifecycle"""
    response_body = {
        'Status': response,
        'StackId': event['StackId'],
        'RequestId': event['RequestId'],
        'LogicalResourceId': event['LogicalResourceId'],
    }
    print('RESPONSE BODY: \n' + dumps(response_body))
    data = dumps(response_body).encode('utf-8')
    req = urllib.request.Request(
        event['ResponseURL'],
        data,
        headers={'Content-Length': len(data), 'Content-Type': ''})
    req.get_method = lambda: 'PUT'
    try:
        with urllib.request.urlopen(req) as resp:
            print(f'response.status: {resp.status}, ' +
                  f'response.reason: {resp.reason}')
            print('response from cfn: ' + resp.read().decode('utf-8'))
    except Exception as e:
        print(e)
        raise Exception('Received non-200 response while sending response to AWS CloudFormation')
    return True


def custom_resource_handler(event, context):
    print("Event JSON: \n" + dumps(event))
    ResourceARN = event['ResourceProperties']['ResourceARN']
    tags = event['ResourceProperties']['Tags']
    response = 'FAILED'
    client = boto3.client('events')
    if event['RequestType'] == 'Create':
        try:
            response = client.tag_resource(
                ResourceARN=ResourceARN,
                Tags=tags)
            response = 'SUCCESS'
        except Exception as e:
            print(e)
        send_response(event, context, response)
        return

    if event['RequestType'] == 'Update':
        # Do nothing and send a success immediately
        send_response(event, context, response)
        return

    if event['RequestType'] == 'Delete':
        try:
            response = client.untag_resource(
                ResourceARN = ResourceARN,
                TagKeys = tags['Key']
            )
            response = 'SUCCESS'
        except Exception as e:
            print(e)
        send_response(event, context, response)


def lambda_handler(event, context):
    """Lambda handler for the custom resource"""
    try:
        return custom_resource_handler(event, context)
    except Exception as e:
        print(e)
        raise

This is the CFN block:

CustomTagEvent:
    Type: Custom::TagEventRule
    Version: "1.0"
    DependsOn: EventRule
    Properties:
      ServiceToken: "LAMBDA_ARN"
      ResourceARN: 
        Fn::GetAtt:
          - "EventRule"
          - "Arn"
      Tags: 
        - 
          Key: Name

While creating CLoudFormation it gave error "CREATE FAILED".

"Invalid PhysicalResourceId"

But, somehow managed to create tags. Need help to understand why it gave the CloudFormation error if it created the tags?


Solution

  • Custom resources require specific output which contains PhysicalResourceId:

    {
       "Status" : "SUCCESS",
       "PhysicalResourceId" : "TestResource1",
       "StackId" : "arn:aws:cloudformation:us-west-2:123456789012:stack/stack-name/guid",
       "RequestId" : "unique id for this create request",
       "LogicalResourceId" : "MyTestResource",
       "Data" : {
          "OutputName1" : "Value1",
          "OutputName2" : "Value2",
       }
    }
    

    Your lambda function does not generate such a response, thus it errors out.

    The best way to create custom resources is by using cfn-response provided by AWS specially for that purpose.