After talking with AWS and back, here are a few code examples that I created that do this.
First of all, if you want to use predefined interfaces to create handlers , you can implement RequestsHandler and define HandleRequest methods as follows:
public class MyCloudFormationResponder implements RequestHandler<Map<String, Object>, Object>{ public Object handleRequest(Map<String,Object> input, Context context) { ... } }
Map<String, Object> is a map of values ββsent from your CloudFormation resource to the Lambda function. CF resource example:
"MyCustomResource": { "Type" : "Custom::String", "Version" : "1.0", "Properties": { "ServiceToken": "arn:aws:lambda:us-east-1:xxxxxxx:function:MyCloudFormationResponderLambdaFunction", "param1": "my value1", "param2": ["t1.micro", "m1.small", "m1.large"] } }
can be analyzed using the following code
String responseURL = (String)input.get("ResponseURL"); context.getLogger().log("ResponseURLInput: " + responseURL); context.getLogger().log("StackId Input: " + input.get("StackId")); context.getLogger().log("RequestId Input: " + input.get("RequestId")); context.getLogger().log("LogicalResourceId Context: " + input.get("LogicalResourceId")); context.getLogger().log("Physical Context: " + context.getLogStreamName()); @SuppressWarnings("unchecked") Map<String,Object> resourceProps = (Map<String,Object>)input.get("ResourceProperties"); context.getLogger().log("param 1: " + resourceProps.get("param1")); @SuppressWarnings("unchecked") List<String> myList = (ArrayList<String>)resourceProps.get("param2"); for(String s : myList){ context.getLogger().log(s); }
The key points here, in addition to what is explained in the NodeJS examples in the AWS documentation, are
(String)input.get("ResponseURL") is a pre-signed S3 URL that you need to respond to (more on this later)(Map<String,Object>)input.get("ResourceProperties") returns a map of your custom CloudFormation "Properties" resource passed to the Lambda function from your CF template. I have provided String and ArrayList as two examples of the types of objects that can be returned, although several others are possible.
To respond to creating an instance of the CloudFormation template custom resource, you need to call the HTTP PUT callback back to the previously mentioned ResponseURL and include most of the following fields in the cloudFormationJsonResponse variable. The following shows how I did it.
try { URL url = new URL(responseURL); HttpURLConnection connection=(HttpURLConnection)url.openConnection(); connection.setDoOutput(true); connection.setRequestMethod("PUT"); OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream()); JSONObject cloudFormationJsonResponse = new JSONObject(); try { cloudFormationJsonResponse.put("Status", "SUCCESS"); cloudFormationJsonResponse.put("PhysicalResourceId", context.getLogStreamName()); cloudFormationJsonResponse.put("StackId", input.get("StackId")); cloudFormationJsonResponse.put("RequestId", input.get("RequestId")); cloudFormationJsonResponse.put("LogicalResourceId", input.get("LogicalResourceId")); cloudFormationJsonResponse.put("Data", new JSONObject().put("CFAttributeRefName", "some String value useful in your CloudFormation template")); } catch (JSONException e) { e.printStackTrace(); } out.write(cloudFormationJsonResponse.toString()); out.close(); int responseCode = connection.getResponseCode(); context.getLogger().log("Response Code: " + responseCode); } catch (IOException e) { e.printStackTrace(); }
Of particular note is the Data node above, which refers to an additional com.amazonaws.util.json.JSONObject , in which I include any attributes that are required in my CloudFormation template. In this case, it will be extracted in the CF template with something like { "Fn::GetAtt": [ "MyCustomResource", "CFAttributeRefName" ] }
Finally, you can simply return null , since nothing will be returned from this function, since it is an HTTPUrlConnection , which actually answers the CF call.