How do we access and respond to CloudFormation user resources using the AWS Lambda feature written in Java?

I have an AWS Lambda function written in Java that I would like to use as part of the response to the AWS CloudFormation function. Amazon provides two detailed examples on how to create a custom CloudFormation resource that returns its value based on AWS. A lambda function written in Node.js, however, it is difficult for me to translate Lambda examples into Java. How can we configure our AWS Java function to read the value of the pre-signed S3 URL passed as a parameter to the Lambda function from CloudFormation and send our desired response to the pending CloudFormation template?

+5
source share
2 answers

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.

+6
source

Nile,

I really appreciate your excellent documentation. I would add a few things that I found useful:

input.get ("RequestType") - this is returned as "Create", "Delete", etc. You can use this value to determine what to do when the stack is created, deleted, etc.

In terms of security, I downloaded Lambda Functions and manually set the VPC, subnets, and security group (default) so that I can reuse it with multiple cloudformationn scripts. Everything seems to be working fine.

I created one Lambda function called by CF scripts, and one of them can be started manually if the first one failed.

This great gradle aws plugin makes it easy to load Java Lambda features into AWS.

Gradle Plugin AWS

0
source

Source: https://habr.com/ru/post/1232412/


All Articles