Updating an AWS CloudFormation with a custom trigger for Lambda - amazon-web-services

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:*:*:*"

Related

Property StageName: not defined for resource of type AWS::Serverless::Api

I have seen alot of post say that you can add OpenApiVersion: '2.0' to fix this problem but it does not change anything in my case. For some reason now that I am trying to add a Stage for the first time and run my function locally with sam local start-api I always get Error: [InvalidResourceException('ApiGateway', 'property StageName: not defined for resource of type AWS::Serverless::Api')] as an error. The reason for adding the new stage is because I need it for my new authorizer I am trying to implement. Any ideas why StageName is not defined? Its clearly needed per AWS documentation
template.json
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Description",
"Transform": [
"AWS::Serverless-2016-10-31"
],
"Resources": {
"RapidApiGateway": {
"Type": "AWS::Serverless::Api",
"Properties": {
"StageName:": "Prod",
"MethodSettings": [
{
"HttpMethod": "*",
"ResourcePath": "/*",
"ThrottlingRateLimit": 10,
"ThrottlingBurstLimit": 10
}
],
"Auth": {
"DefaultAuthorizer": "RapidAuthorizer",
"Authorizers": {
"RapidAuthorizer": {
"Fn::GetAtt": [
"RapidAuthFunction",
"attributeName"
],
"Identity": {
"Headers": [
"X-RapidAPI-Proxy-Secret"
]
}
}
}
}
}
},
"RapidAuthFunction": {
"Type": "AWS::Serverless::Function",
"Properties": {
"CodeUri": "./authorizer",
"Handler": "handler.authorizer",
"Runtime": "nodejs14.x"
}
}
},
"Outputs": {
"WebEndpoint": {
"Description": "API Gateway endpoint URL for Prod stage",
"Value": {
"Fn::Sub": "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"
}
}
}
}

Unable to invoke lambda function in api gateway method using cloudformation template

I am trying to provision Lambda and API Gateway using CloudFormation template and trying to invoke Lambda in one of the method, but getting issue like function arn is invalid. Please provide some resolution on this
{
"AWSTemplateFormatVersion":"2010-09-09",
"Transform":"AWS::Serverless-2016-10-31",
"Description":"Testing.",
"Parameters":{
},
"Conditions":{
},
"Resources":{
"AspNetCoreFunction":{
"Type":"AWS::Serverless::Function",
"Properties":{
"Handler":"<Handler_details>",
"Runtime":"dotnetcore3.1",
"CodeUri":"",
"MemorySize":256,
"Timeout":30,
"Role":"$Function_Role",
"Policies":null,
}
},
"gateway":{
"Type":"AWS::ApiGateway::RestApi",
"Properties":{
"Name":"Test",
"Description":"Testing",
"ApiKeySourceType":"HEADER",
"EndpointConfiguration":{
"Types":[
"REGIONAL"
]
}
}
},
"ApiMethod":{
"Type":"AWS::ApiGateway::Method",
"Properties":{
"RestApiId":{
"Ref":"gateway"
},
"HttpMethod":"ANY",
"AuthorizationType":"NONE",
"Integration":{
"IntegrationHttpMethod":"POST",
"TimeoutInMillis":29000,
"Type":"AWS_PROXY",
"Uri":{
"Fn::Sub":[
"arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations",
{
"lambdaArn":{
"Fn::GetAtt":[
"AspNetCoreFunction",
"Arn"
]
}
}
]
}
}
}
}
}
I have tried to use below uri, behalf above uri which is mentioned in above code, but still having same issue.
"uri": "Fn::Sub":["arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/${AspNetCoreFunction.Arn}/invocations"]
I think the error is not about ARN, but about API gateway not having permissions to invoke your function. You need to add something like the following:
{
"AllowApiInvokations": {
"Type": "AWS::Lambda::Permission",
"Properties": {
"Action": "lambda:InvokeFunction",
"FunctionName": "<function-name>" ,
"Principal": "apigateway.amazonaws.com"
}
}
}

Pass parameters to AWS Cloudwatch event target lambda function

I want to pass parameters to my lambda function invoked by AWS Cloudwatch events. The parameter name is alarmActions and my CFT template for the event rule is as follows:
"LambdaInvokeScheduler": {
"Type": "AWS::Events::Rule",
"Properties": {
"Description": "Scheduled Rule for invoking lambda function",
"EventPattern": {
"source": [
"aws.ecs"
],
"detail-type": [
"ECS Container Instance State Change"
],
"detail": {
"clusterArn": [
{ "Fn::GetAtt": ["WindowsCluster", "Arn"] }
]
}
},
"State": "ENABLED",
"Targets": [{
"Arn": { "Fn::GetAtt": ["AlarmCreationLambdaFunction", "Arn"] },
"Id": "AlarmCreationLambdaFunction",
"Input": { "Fn::Join" : ["", [ "{ \"alarmActions\": \"", { "Fn::Join" : [":", [ "arn:aws:sns", { "Ref" : "AWS::Region" }, { "Ref" : "AWS::AccountId" }, "CloudWatch"]] }, "\" }"]] }
}]
}
}
I have used the Input parameter to pass a JSON text. There is not much documentation around it. I just wanted to find the right way to do it.
I found the solution. I was referring the parameter in lambda in a wrong way.
My lambda function was like this:
def func(event, context, alarmActions)
{
print(alarmActions)
}
It worked when i made the following update:
def func(event, context)
{
print(event['alarmActions'])
}

CloudFormation Custom Resource responseKey

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.

How to invoke a series of API calls to an application in an AWS instance using cloudformation

Is there a way to create a cloudformation template, which invokes REST API calls to an EC2 instance ?
The use case is to modify the configuration of the application without having to use update stack and user-data, because user-data updation is disruptive.
I did search through all the documentation and found that this could be done by calling an AWS lambda. However, unable to get the right combination of CFM template and invocation properties.
Adding a simple lambda, which works stand-alone :
from __future__ import print_function
import requests
def handler(event, context):
r1=requests.get('https://google.com')
message = r1.text
return {
'message' : message
}
This is named as ltest.py, and packaged into ltest.zip with requests module, etc. ltest.zip is then called in the CFM template :
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "Test",
"Parameters": {
"ModuleName" : {
"Description" : "The name of the Python file",
"Type" : "String",
"Default" : "ltest"
},
"S3Bucket" : {
"Description" : "The name of the bucket that contains your packaged source",
"Type" : "String",
"Default" : "abhinav-temp"
},
"S3Key" : {
"Description" : "The name of the ZIP package",
"Type" : "String",
"Default" : "ltest.zip"
}
},
"Resources" : {
"AMIInfo": {
"Type": "Custom::AMIInfo",
"Properties": {
"ServiceToken": { "Fn::GetAtt" : ["AMIInfoFunction", "Arn"] }
}
},
"AMIInfoFunction": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Code": {
"S3Bucket": { "Ref": "S3Bucket" },
"S3Key": { "Ref": "S3Key" }
},
"Handler": { "Fn::Join" : [ "", [{ "Ref": "ModuleName" },".handler"] ]},
"Role": { "Fn::GetAtt" : ["LambdaExecutionRole", "Arn"] },
"Runtime": "python2.7",
"Timeout": "30"
}
},
"LambdaExecutionRole": {
"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:CreateLogGroup","logs:CreateLogStream","logs:PutLogEvents"],
"Resource": "arn:aws:logs:*:*:*"
},
{
"Effect": "Allow",
"Action": ["ec2:DescribeImages"],
"Resource": "*"
}]
}
}]
}
}
},
"Outputs" : {
"AMIID" : {
"Description": "Result",
"Value" : { "Fn::GetAtt": [ "AMIInfo", "message" ] }
}
}
}
The result of the above (with variations of the Fn::GetAtt call) is that the Lambda gets instantiated, but the AMIInfo call is stuck in "CREATE_FUNCTION".
The stack also does not get deleted properly.
I would attack this with Lambda, but it seems as though you already thought of that and might be dismissing it.
A little bit of a hack, but could you add Files to the instance via Metadata where the source is the REST url?
e.g.
"Type": "AWS::EC2::Instance",
"Metadata": {
"AWS::CloudFormation::Init": {
"configSets": {
"CallREST": [ "CallREST" ]
},
"CallREST": { "files":
{ "c://cfn//junk//rest1output.txt": { "source": "https://myinstance.com/RESTAPI/Rest1/Action1" } } },
}
}
To fix your lambda you need to signal SUCCESS. When CloudFormation creates (and runs) the Lambda, it expected that the Lambda signal success. This is the reason you are getting the stuck "CREATE_IN_PROGRESS"
At the bottom of http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html is a function named "send" to help you signal success.
and here's my attempt to integrate it into your function AS PSUEDOCODE without testing it, but you should get the idea.
from __future__ import print_function
import requests
def handler(event, context):
r1=requests.get('https://google.com')
message = r1.text
# signal complete to CFN
# send(event, context, responseStatus, responseData, physicalResourceId)
send(..., ..., SUCCESS, ...)
return {
'message' : message
}
Lambda triggered by the event. Lifecycle hooks can be helpful.
You can hack CoudFormation, but please mind: it is not designed for this.