0
votes

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.

2
sounds like you want to use a Custom Resource: docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/…at0mzk
Came across Custom Resources after posting the question. Exploring this. Thanks !Abhinav

2 Answers

2
votes

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
  }  
0
votes

Lambda triggered by the event. Lifecycle hooks can be helpful. You can hack CoudFormation, but please mind: it is not designed for this.