CloudFormation Custom Resource responseKey - amazon-web-services

I have got lambda backed Custom Stack in CloudFormation , So I need the fetch function output and put it to the AWS Console, how I can handle this problem?
My Stack is shown as below ;
"CreateExistingVPC": {
"Type": "Custom::CreateExistingVPC",
"Properties": {
"ServiceToken": { "Fn::If": ["LambdaAvailable",{ "Fn::GetAtt": [ "CustomLogic", "Outputs.LambdaAttachHostedZoneArn" ] }, { "Ref": "AWS::NoValue" } ] },
"Region": { "Ref": "AWS::Region" },
"HostedZoneId": { "Ref": "InternalHostedZone" },
"VpcId": { "Fn::GetAtt": [ "VPC", "Outputs.VPC" ] }
}
}
},
"Outputs": {
"Route53VPC": {
"Description": "ExistingRoute53VPCStatus",
"Value": { "Fn::GetAtt": [ "CreateExistingVPC", "{ ??????? }" ] }
}
}
In actually, I have found some answers but 'response key' not worked in my case , how I can find response key ??
AWS Cloudformation, Output value from Custom Resource

You need to use the variable you are using to return your response. e.g. (nodeJs)
module.exports.createPoolList = (event, context, callback) => {
if (event.RequestType == 'Create') {
let array = event.ResourceProperties.OpsPoolArnList.split(",");
array.push(event.ResourceProperties.UserPool);
let response = {
'list': array.join(),
};
sendresponse(event, "SUCCESS", response, "");
}
if (event.RequestType == 'Delete') {
sendresponse(event, "SUCCESS", null, "");
}
callback(null, "");
};
Here list is the variable which contains my output & returning in my response. The built payload looks like
let payload = {
'StackId': event.StackId,
'Status' : responsestatus,
'Reason' : reason,
'RequestId': event.RequestId,
'LogicalResourceId': event.LogicalResourceId,
'PhysicalResourceId': event.LogicalResourceId + 'qwerty',
'Data': response
};
And I refer to this in my script as
!GetAtt <ResourceName>.list
Hope it helps.

Related

Can we use GET method in api-gateway to invoke step function?

I am invoking step function from api-gateway. For POST request it is working fine. Can I use GET request as well? as we we have to pass state-machine ARN in body or mapping template. Is there any work around?
Below is my cloud formation template:
"paths": {
"/individual": {
"get": {
"operationId": "GET HTTP",
"responses": {
"200": {
"description": "200 response"
}
},
"x-amazon-apigateway-integration": {
"type": "AWS",
"httpMethod": "GET",
"uri": "arn:aws:apigateway:eu-west-1:states:action/StartExecution",
"credentials": {
"Fn::GetAtt": [
"apiGatewayIamRoleGet",
"Arn"
]
},
"requestTemplates": {
"application/json": {
"Fn::Sub": [
"#set($body= $input.json('$'))\n #set($inputRoot='{ \"inputData\" :'+$body+',\"apiInfo\":{\"httpMethod\" :\"'+ $context.httpMethod+'\",\"apiKey\" :\"'+ $context.identity.apiKey+'\"}}')\n #set($apiData=$util.escapeJavaScript($inputRoot))\n #set($apiData=$apiData.replaceAll(\"\\'\",\"'\"))\n {\n \"input\" :\"$apiData\",\n \"stateMachineArn\": \"${StepFunctionArn}\" \n }",
{
"StepFunctionArn": {
"Ref": "StepFunctionGet"
}
}
]
}
},
"payloadFormatVersion": 1.0,
"responses": {
"default": {
"statusCode": "200"
}
}
}
}
}
}
I have sorted out the issue. httpMethod in api-gateway integration needs to be POST I used GET there which causing error.

Unable to send GET request with AWS Lambda & DynamoDB Rest API using serverless

I am creating an API to make GET and POST request to a table in DynamoDB.
I deployed it using serverless and received the endpoints for each API type.
But when testing it out with Postman I get the following error:
Bad request. We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner.
If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation.
Code for creating the data in the table:
const postsTable = process.env.POSTS_TABLE;
// Create a response
function response(statusCode, message) {
return {
statusCode: statusCode,
body: JSON.stringify(message)
};
}
// Create a post
module.exports.createPost = (event, context, callback) => {
const reqBody = JSON.parse(event.body);
if (
!reqBody.title ||
reqBody.title.trim() === "" ||
!reqBody.body ||
reqBody.body.trim() === ""
) {
return callback(
null,
response(400, {
error:
"Post must have a title and body and they must not be empty"
})
);
}
const post = {
id: uuidv4(),
createdAt: new Date().toISOString(),
userId: 1,
title: reqBody.title,
body: reqBody.body
};
return db
.put({
TableName: postsTable,
Item: post
})
.promise()
.then(() => {
callback(null, response(201, post));
})
.catch(err => response(null, response(err.statusCode, err)));
};
I managed to do it but did not use Serverless.
I set up Lambda functions to POST and GET the data from a url.
I think the issue previously was to do with the policies. This time when making the Lambda functions I set it as the following:
I clicked on "Create a new role from AWS policy templates" while creating an execution role for a new function, then selected "Simple microservice permissions" for Policy templates. This added Basic execution role policy and below DynamoDB permissions to the role for all the tables in the same region as the function :
"Action": [
"dynamodb:DeleteItem",
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:Scan",
"dynamodb:UpdateItem"
]
Lambda function for POST request
exports.handler = async (event, context) => {
const ddb = new AWS.DynamoDB({ apiVersion: "2012-10-08" });
const documentClient = new AWS.DynamoDB.DocumentClient({
region: "ap-southeast-1"
});
let responseBody = "";
let statusCode = 0;
const {
deviceId,
batteryLevel,
eventId,
id,
location,
tags,
time
} = JSON.parse(event.body);
const params = {
TableName: "dashboard",
Item: {
batteryLevel: batteryLevel,
deviceId: deviceId,
eventId: eventId,
location: location,
tags: tags,
time: time
}
};
try {
const data = await documentClient.put(params).promise();
responseBody = JSON.stringify(data);
statusCode = 201;
} catch (err) {
responseBody = "Unable to POST data";
statusCode = 403;
}
const response = {
statusCode: statusCode,
headers: {
myHeader: "test"
},
body: responseBody
};
return response;
};
Other issues as well were with the method execution of the API I needed to set a custom model for the Request Body to match my data:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "DashboardInputModel",
"type": "object",
"properties":
{
"batteryLevel": {"type": "string"},
"deviceId": {"type": "string"},
"eventId": {"type": "string"},
"id": {"type": "number"},
"location": {
"type": "object",
"properties":{
"accuracy": {"type": "number"},
"latitude": {"type": "number"},
"longitude": {"type": "number"}
}
},
"tags": {
"type": "array",
"items": {
"type": "object",
"properties": {
"accelX":{"type": "number"},
"accelY": {"type": "number"},
"accelZ": {"type": "number"},
"createDate": {"type": "string"},
"dataFormat":{"type": "number"},
"defaultBackground": {"type": "number"},
"favorite": {"type": "boolean"},
"humidity": {"type": "number"},
"id": {"type": "string"},
"measurementSequenceNumber": {"type": "number"},
"movementCounter": {"type": "number"},
"name": {"type": "string"},
"pressure": {"type": "number"},
"rssi": {"type": "number"},
"temperature": {"type": "number"},
"txPower":{"type": "number"},
"updateAt": {"type": "string"},
"voltage": {"type": "number"}
}
}
},
"time": {"type": "string"}
}
}
For each action I also enabled CORS and replaced the existing CORS headers.
These two videos explains the entire process much better than the documentation and I hope it helps.
Part 1
Part 2
By bad request do you mean Status Code 400? It could simply be that you are not correctly calling your API.
If you are getting a 403 then you need to pass through that you are authorised to access the resource you are trying to get. You can see how to do this through the AWS docs.
This page includes a link to an example.
List of error codes.

Cognito Lambda trigger socket timeout [duplicate]

This question already has an answer here:
AWS Lambda can't call Cognito Identity - IAM Role
(1 answer)
Closed 4 years ago.
I have a Lambda trigger on my cognito resource for the presignup trigger.
I am following this example
https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-sign-up.html#aws-lambda-triggers-pre-registration-example
I am getting a socket timeout and not finding much documentation on this
{
"err": {
"code": "UnexpectedLambdaException",
"name": "UnexpectedLambdaException",
"message": "arn:aws:lambda:region-arn:function:confirm failed with error Socket timeout while invoking Lambda function."
}
}
My resources are defined as so:
"ConfirmPermission" : {
"Type" : "AWS::Lambda::Permission",
"Properties" : {
"Action" : "lambda:InvokeFunction",
"FunctionName" : { "Fn::GetAtt" : [ "confirm", "Arn" ] },
"Principal" : "cognito-idp.amazonaws.com",
"SourceArn" : { "Fn::GetAtt" : [ "Auth", "Arn" ] }
}
},
"confirm" : {
"Type": "AWS::Serverless::Function",
"Properties": {
"Handler": "index.confirm",
"Runtime": "nodejs8.10",
"CodeUri": "./src",
"FunctionName": "confirm",
"ReservedConcurrentExecutions" : 15,
"Timeout": 50,
"Role": "arn:aws:iam::arn:role/lambda-vpc-role"
}
},
"AuthApp" : {
"Type" : "AWS::Cognito::UserPoolClient",
"Properties" : {
"UserPoolId" : {"Ref" : "Auth"}
}
},
"Auth" : {
"Type" : "AWS::Cognito::UserPool",
"Properties": {
"LambdaConfig" : {
"PreSignUp" : { "Fn::GetAtt" : [ "confirm", "Arn" ] }
},
"Schema" : [
{
"AttributeDataType": "String",
"Name": "email",
"Mutable": true,
"Required": true
},
{
"AttributeDataType": "String",
"Name": "family_name",
"Mutable": true,
"Required": true
},
{
"AttributeDataType": "String",
"Name": "given_name",
"Mutable": true,
"Required": true
}
],
"UsernameAttributes": ["email"]
}
}
Lambda Functions:
index.js
let signIn = require('Auth/Auth.js');
exports.signIn = signIn.signIn;
exports.signUp = signIn.signUp;
exports.confirm = signIn.confirm;
sign up / confirm
exports.signUp = async (event, context) => {
var body = JSON.parse(event.body);
var emailAttribute = {
Name : 'email',
Value: body.email
};
var firstNameAttribute = {
Name: 'given_name',
Value: body.firstName
};
var lastNameAttribute = {
Name: 'family_name',
Value: body.lastName
};
var attributeList = [emailAttribute, firstNameAttribute, lastNameAttribute];
try {
var cognitoUser = await cognitoSignUp(body, attributeList);
return {
statusCode : 200,
body : JSON.stringify({res : cognitoUser})
};
} catch(e) {
return {
statusCode : 500,
body : JSON.stringify({err : e})
};
}
}
exports.confirm = (event, context, callback) => {
event.response.autoConfirmUser = true;
callback(null, event);
return;
}
var cognitoSignUp = (body, attributeList) => new Promise((acc, rej) => {
userPool.signUp(body.email, body.password, attributeList, null, function(err, res) {
if (err) {
console.log('ERROR');
console.log(err);
rej(err);
} else {
console.log('SUCCSSS');
acc(res);
}
});
});
Any idea on what is causing this?
As it turns out, it's because the confirm function has an IAM role for aws resources which blocks network requests. I don't need the IAM role on this function. After removing it, it works perfectly. If you do need resource access, then you have to use a nat gateway
See more here:
AWS Lambda can't call Cognito Identity - IAM Role
https://gist.github.com/reggi/dc5f2620b7b4f515e68e46255ac042a7
You can increase the timeout of your function up to 15 minutes 900 seconds as shown below...
"confirm" : {
"Type": "AWS::Serverless::Function",
"Properties": {
"Handler": "index.confirm",
"Runtime": "nodejs8.10",
"CodeUri": "./src",
"FunctionName": "confirm",
"ReservedConcurrentExecutions" : 15,
"Timeout": 900,
"Role": "arn:aws:iam::arn:role/lambda-vpc-role"
}
},
Unless you're encoding something running some long analytics you probably shouldn't even need the to 50 seconds let alone 15 minutes though. you should check your logs for an error somewhere and make sure you're calling...
callback(null, event);
To return response from the Lambda.

Cloudformation does not support create vpc links in apigateway

In aws api gateway there is a section called API Link and I can manually set that.
The problem is I cannot find any section in cloudformation documentation on how I can create vpc link via cloud formation on api gateway.
Is it sth that cloudformation does not support or am I missing it?
You can use swagger to define an API Gateway using VPC Link. This is a complete CloudFormation template you can deploy to test it out...
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Test backend access via API Gateway. This template provisions a Regional API Gateway proxing requests to a backend via VPC Link and Direct Connect to on-premises resources using private ip addresses.",
"Parameters": {
"VPCId": {
"Description": "VPC Id for API Gateway VPC Link",
"Type": "AWS::EC2::VPC::Id"
},
"NLBSubnetList": {
"Type": "List<AWS::EC2::Subnet::Id>",
"Description": "Subnet Ids for provisioning load balancer supporting the VPC Link"
},
"BackendBaseEndpoint": {
"Description": "The backend service base url including protocol. e.g.: https://<url>",
"Type": "String",
"Default": "https://mybackend.dev.mycompany.com"
},
"TargetIpAddresses": {
"Type": "CommaDelimitedList",
"Description": "Comma separated list of NLB target ip addresses. Specify two entries.",
"Default": "10.78.80.1, 10.79.80.1"
}
},
"Resources": {
"API": {
"Type": "AWS::ApiGateway::RestApi",
"Properties": {
"Name": "Test Api",
"Description": "Test Api using VPC_LINK and AWS_IAM authorisation",
"Body": {
"swagger": "2.0",
"info": {
"title": "Test Api"
},
"schemes": [
"https"
],
"paths": {
"/{proxy+}": {
"x-amazon-apigateway-any-method": {
"parameters": [
{
"name": "proxy",
"in": "path",
"required": true,
"type": "string"
}
],
"responses": {},
"security": [
{
"sigv4": []
}
],
"x-amazon-apigateway-integration": {
"responses": {
"default": {
"statusCode": "200"
}
},
"requestParameters": {
"integration.request.path.proxy": "method.request.path.proxy"
},
"uri": {
"Fn::Join": [
"",
[
{
"Ref": "BackendBaseEndpoint"
},
"/{proxy}"
]
]
},
"passthroughBehavior": "when_no_match",
"connectionType": "VPC_LINK",
"connectionId": "${stageVariables.vpcLinkId}",
"httpMethod": "GET",
"type": "http_proxy"
}
}
}
},
"securityDefinitions": {
"sigv4": {
"type": "apiKey",
"name": "Authorization",
"in": "header",
"x-amazon-apigateway-authtype": "awsSigv4"
}
}
},
"EndpointConfiguration": {
"Types": [
"REGIONAL"
]
}
},
"DependsOn": "VPCLink"
},
"APIStage": {
"Type": "AWS::ApiGateway::Stage",
"Properties": {
"StageName": "dev",
"Description": "dev Stage",
"RestApiId": {
"Ref": "API"
},
"DeploymentId": {
"Ref": "APIDeployment"
},
"MethodSettings": [
{
"ResourcePath": "/*",
"HttpMethod": "GET",
"MetricsEnabled": "true",
"DataTraceEnabled": "true",
"LoggingLevel": "ERROR"
}
],
"Variables": {
"vpcLinkId": {
"Ref": "VPCLink"
}
}
}
},
"APIDeployment": {
"Type": "AWS::ApiGateway::Deployment",
"Properties": {
"RestApiId": {
"Ref": "API"
},
"Description": "Test Deployment"
}
},
"VPCLink": {
"Type": "AWS::ApiGateway::VpcLink",
"Properties": {
"Description": "Vpc link to GIS platform",
"Name": "VPCLink",
"TargetArns": [
{
"Ref": "NLB"
}
]
}
},
"NLBTargetGroup": {
"Type": "AWS::ElasticLoadBalancingV2::TargetGroup",
"Properties": {
"Name": "NLBTargetGroup",
"Port": 443,
"Protocol": "TCP",
"TargetGroupAttributes": [
{
"Key": "deregistration_delay.timeout_seconds",
"Value": "20"
}
],
"TargetType": "ip",
"Targets": [
{
"Id": { "Fn::Select" : [ "0", {"Ref": "TargetIpAddresses"} ] },
"Port": 443,
"AvailabilityZone": "all"
},
{
"Id": { "Fn::Select" : [ "1", {"Ref": "TargetIpAddresses"} ] },
"Port": 443,
"AvailabilityZone": "all"
}
],
"VpcId": {
"Ref": "VPCId"
},
"Tags": [
{
"Key": "Project",
"Value": "API and VPC Link Test"
}
]
}
},
"NLB": {
"Type": "AWS::ElasticLoadBalancingV2::LoadBalancer",
"Properties": {
"Type": "network",
"Scheme": "internal",
"Subnets": {
"Ref": "NLBSubnetList"
}
}
},
"NLBListener": {
"Type": "AWS::ElasticLoadBalancingV2::Listener",
"Properties": {
"DefaultActions": [
{
"Type": "forward",
"TargetGroupArn": {
"Ref": "NLBTargetGroup"
}
}
],
"LoadBalancerArn": {
"Ref": "NLB"
},
"Port": "443",
"Protocol": "TCP"
}
}
},
"Outputs": {
"NetworkLoadBalancerArn": {
"Value": {
"Ref": "NLB"
},
"Description": "The network elastic load balancer Amazon resource name"
}
}
}
Unfortunately, CloudFormation does not support API Gateway's VPC Links at this time.
You can create a Lambda-backed custom resource to manage the VPC Link using CloudFormation.
Here is a Lambda function (using python3.6) for a CloudFormation custom resource I use to manage VPC links:
import copy
import json
import re
import time
import boto3
from botocore.vendored import requests
SUCCESS = "SUCCESS"
FAILED = "FAILED"
FAILED_PHYSICAL_RESOURCE_ID = "FAILED_PHYSICAL_RESOURCE_ID"
class AddOrUpdateTargetArnsError(Exception):
def __init__(self):
self.message = 'Target arns are not allowed to be changed/added.'
super().__init__(self.message)
class FailedVpcLinkError(Exception):
def __init__(self, status_message):
self.message = f'statusMessages: {status_message}'
super().__init__(self.message)
def lambda_handler(event, context):
try:
_lambda_handler(event, context)
except Exception as e:
send(
event,
context,
response_status=FAILED,
# Do not fail on delete to avoid rollback failure
response_data=None,
physical_resource_id=event.get('PhysicalResourceId', FAILED_PHYSICAL_RESOURCE_ID),
reason=e
)
# Must raise, otherwise the Lambda will be marked as successful, and the exception
# will not be logged to CloudWatch logs.
raise
def _lambda_handler(event, context):
print("Received event: ")
print(event)
resource_type = event['ResourceType']
if resource_type != "Custom::ApiGatewayVpcLink":
raise ValueError(f'Unexpected resource_type: {resource_type}')
request_type = event['RequestType']
wait_for = event.get('WaitFor', None)
resource_properties = event['ResourceProperties']
physical_resource_id = event.get('PhysicalResourceId', None)
apigateway = boto3.client('apigateway')
if wait_for:
handle_self_invocation(
wait_for=wait_for,
physical_resource_id=physical_resource_id,
event=event,
context=context,
)
else:
if request_type == 'Create':
kwargs = dict(
name=resource_properties['Name'],
targetArns=resource_properties['TargetArns'],
description=resource_properties.get('Description', None)
)
response = apigateway.create_vpc_link(**kwargs)
event_copy = copy.deepcopy(event)
event_copy['WaitFor'] = 'CreateComplete'
event_copy['PhysicalResourceId'] = response['id']
print('Reinvoking function because VPC link creation is asynchronous')
relaunch_lambda(event=event_copy, context=context)
return
elif request_type == 'Update':
old_resource_properties = event['OldResourceProperties']
current_target_arns = apigateway.get_vpc_link(
vpcLinkId=physical_resource_id,
)['targetArns']
# must compare current_target_arns to resource_properties['TargetArns'], to protect against
# UPDATE created by UPDATE_FAILED. In that particular case, current_target_arns will be the same as
# resource_properties['TargetArns'] but different than old_resource_properties['TargetArns']
if set(current_target_arns) != set(resource_properties['TargetArns']) and \
set(resource_properties['TargetArns']) != set(old_resource_properties['TargetArns']):
raise AddOrUpdateTargetArnsError()
patch_operations = []
if resource_properties['Name'] != old_resource_properties['Name']:
patch_operations.append(dict(
op='replace',
path='/name',
value=resource_properties['Name'],
))
if 'Description' in resource_properties and 'Description' in old_resource_properties:
if resource_properties['Description'] != old_resource_properties['Description']:
patch_operations.append(dict(
op='replace',
path='/description',
value=resource_properties['Description'],
))
elif 'Description' in resource_properties and 'Description' not in old_resource_properties:
patch_operations.append(dict(
op='replace',
path='/description',
value=resource_properties['Description'],
))
elif 'Description' not in resource_properties and 'Description' in old_resource_properties:
patch_operations.append(dict(
op='replace',
path='/description',
value=None,
))
apigateway.update_vpc_link(
vpcLinkId=physical_resource_id,
patchOperations=patch_operations,
)
elif request_type == 'Delete':
delete = True
if physical_resource_id == FAILED_PHYSICAL_RESOURCE_ID:
delete = False
print('Custom resource was never properly created, skipping deletion.')
stack_name = re.match("arn:aws:cloudformation:.+:stack/(?P<stack_name>.+)/.+", event['StackId']).group('stack_name')
if stack_name in physical_resource_id:
delete = False
print(f'Skipping deletion, because VPC link was not created properly. Heuristic: stack name ({stack_name}) found in physical resource ID ({physical_resource_id})')
logical_resource_id = event['LogicalResourceId']
if logical_resource_id in physical_resource_id:
delete = False
print(f'Skipping deletion, because VPC link was not created properly. Heuristic: logical resource ID ({logical_resource_id}) found in physical resource ID ({physical_resource_id})')
if delete:
apigateway.delete_vpc_link(
vpcLinkId=physical_resource_id
)
event_copy = copy.deepcopy(event)
event_copy['WaitFor'] = 'DeleteComplete'
print('Reinvoking function because VPC link deletion is asynchronous')
relaunch_lambda(event=event_copy, context=context)
return
else:
print(f'Request type is {request_type}, doing nothing.')
send(
event,
context,
response_status=SUCCESS,
response_data=None,
physical_resource_id=physical_resource_id,
)
def handle_self_invocation(wait_for, physical_resource_id, event, context):
apigateway = boto3.client('apigateway')
if wait_for == 'CreateComplete':
print('Waiting for creation of VPC link: {vpc_link_id}'.format(vpc_link_id=physical_resource_id))
response = apigateway.get_vpc_link(
vpcLinkId=physical_resource_id,
)
status = response['status']
print('Status of VPC link {vpc_link_id} is {status}'.format(vpc_link_id=physical_resource_id, status=status))
if status == 'AVAILABLE':
send(
event,
context,
response_status=SUCCESS,
response_data=None,
physical_resource_id=physical_resource_id,
)
elif status == 'FAILED':
raise FailedVpcLinkError(status_message=response['statusMessage'])
elif status == 'PENDING':
# Sleeping here to avoid polluting CloudWatch Logs by reinvoking the Lambda too quickly
time.sleep(30)
relaunch_lambda(event, context)
else:
print('Unexpected status, doing nothing')
elif wait_for == 'DeleteComplete':
print('Waiting for deletion of VPC link: {vpc_link_id}'.format(vpc_link_id=physical_resource_id))
try:
response = apigateway.get_vpc_link(
vpcLinkId=physical_resource_id,
)
except apigateway.exceptions.NotFoundException:
print('VPC link {vpc_link_id} deleted successfully'.format(vpc_link_id=physical_resource_id))
send(
event,
context,
response_status=SUCCESS,
response_data=None,
physical_resource_id=physical_resource_id,
)
else:
status = response['status']
assert status == 'DELETING', f'status is {status}'
# Sleeping here to avoid polluting CloudWatch Logs by reinvoking the Lambda too quickly
time.sleep(10)
relaunch_lambda(event, context)
else:
raise ValueError(f'Unexpected WaitFor: {wait_for}')
def relaunch_lambda(event, context):
boto3.client("lambda").invoke(
FunctionName=context.function_name,
InvocationType='Event',
Payload=json.dumps(event),
)
def send(event, context, response_status, response_data, physical_resource_id, reason=None):
response_url = event['ResponseURL']
response_body = {
'Status': response_status,
'Reason': str(reason) if reason else 'See the details in CloudWatch Log Stream: ' + context.log_stream_name,
'PhysicalResourceId': physical_resource_id,
'StackId': event['StackId'],
'RequestId': event['RequestId'],
'LogicalResourceId': event['LogicalResourceId'],
'Data': response_data,
}
json_response_body = json.dumps(response_body)
headers = {
'content-type': '',
'content-length': str(len(json_response_body))
}
try:
requests.put(
response_url,
data=json_response_body,
headers=headers
)
except Exception as e:
print("send(..) failed executing requests.put(..): " + str(e))

Updating an AWS CloudFormation with a custom trigger for Lambda

A team member and I have a CloudFormation stack with a nodejs Lambda backed custom resource.
Upon updating the lambda/parameters/trigger, we would like the Lambda to first delete the 3rd party resources it made and then create new ones based on the new parameters.
Here is our exports.handler for the lambda.
if (event.RequestType == "Delete") {
console.log("Request type == Delete")
var successCallback = function(event, context) {
sendResponse(event, context, "SUCCESS");
}
doDeleteThings(event, context, successCallback);
} else if (event.RequestType == "Create") {
console.log("request type == create")
doCreateThings(event, context);
} else if (event.RequestType == "Update") {
console.log("request type == update")
var successCallback = function(event, context) {
doCreateThings(event, context);
}
doDeleteThings(event, context, successCallback);
} else {
sendResponse(event, context, "SUCCESS");
}
We have tested the code and it works for both create and delete in CloudFormation, and create, delete and update in stackless-mode (where we set: event.RequestType = process.env.RequestType and sendResponse doesn't do the usual CloudFormation response POSTing, but instead just does context.done()), but we cannot seem to make it work on update in CloudFormation. I'm beginning to think that we are misunderstanding what 'update' on a Lambda is supposed to do.
It doesn't help that we've never been able to see CloudWatch logs for Lambda functions created by CloudFormation before.
Here is the relative portion of the CloudFormation template:
"ManageThirdPartyResources": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Code": {
"S3Bucket": "<bucketname>",
"S3Key": "<zipname>.zip"
},
"Description": { "Fn::Join": ["", ["Use cloudformation to automatically create third party resources for the ", { "Ref": "ENV" }, "-", { "Ref": "AWS::StackName" }, " environment"]] },
"Environment": {
"Variables": {
<environment variables that will probably be the things changing.>
}
},
"FunctionName": {
"Fn::Join": ["_", [{ "Ref": "AWS::StackName" }, "ManageThirdPartyResources"]]
},
"Handler": "index.handler",
"Role": "<role>",
"Runtime": "nodejs4.3",
"Timeout": 30
}
},
"ThirdPartyResourcesTrigger": {
"Type": "Custom::ThirdPartyResourcesTrigger",
"Properties": {
"ServiceToken": { "Fn::GetAtt": ["ManageThirdPartyResources", "Arn"] }
}
},
Thanks!
Updates will be triggered on your Custom::ThirdPartyResourcesTrigger if one of its properties change. If properties on the Lambda function change, it will not trigger an update on the Custom::ThirdPartyResourcesTrigger.
So if you want to trigger updates on Custom::ThirdPartyResourcesTrigger, you must modify its properties. For example, you can add a property to ThirdPartyResourcesTrigger called ThingName, and whenever you change the value of ThingName, your Lambda will be called with the Update request type:
"ThirdPartyResourcesTrigger": {
"Type": "Custom::ThirdPartyResourcesTrigger",
"Properties": {
"ServiceToken": { "Fn::GetAtt": ["ManageThirdPartyResources", "Arn"] },
"ThingName": "some value"
}
},
As for logging, make sure the IAM role assumed by your Lambda function has the required permissions for CloudWatch logs:
"Effect": "Allow"
"Action": "logs:*"
"Resource": "arn:aws:logs:*:*:*"