How do I use AWS SAM to build a Lambda-backed custom resource in Java for CloudFormation?
I want to use Java to build an AWS Lambda-backed custom resource to implement in AWS CloudFormation.
Short description
Building a Java-based custom resource in Lambda for CloudFormation is a complex process, especially if you do this process manually. To set up the environment manually, you need to set up the Java development kit and runtime before you build the resource. Then, you need to package and upload the code to Amazon Simple Storage Service (Amazon S3) before you finally create the Lambda function.
To simplify this process, you can use the AWS Serverless Application Model (AWS SAM). AWS SAM is an open-source framework that you can use to build serverless applications on AWS. Use this service to help build the Java custom resource, upload your codes, and deploy the custom resource and Lambda function.
Resolution
Create an instance for development
1. Create an Amazon Elastic Compute Cloud (Amazon EC2) instance to develop your resource. You can use any environment for your instance, but it's a best practice to create your instance in Amazon Linux 2 for this use case.
2. Create and assign an Amazon EC2 SSH key pair to your EC2 instance.
3. Set up an instance profile with permissions to deploy a stack and resource. Specifically, grant permissions to perform the following actions:
- Create a Lambda function
- Update function code
- Invoke a function
- Create a log group that stores the Lambda's log
4. After you launch your instance, log in with SSH. Use this instance as your development environment in the following section.
Set up your development environment
Important: Before you begin, install and configure the AWS Command Line Interface (AWS CLI). If you receive errors when running AWS CLI commands, make sure that you’re using the most recent version of the AWS CLI.
Install java-corretto11
To install corretto11, run the following command:
sudo rpm --import https://yum.corretto.aws/corretto.key sudo curl -L -o /etc/yum.repos.d/corretto.repo https://yum.corretto.aws/corretto.repo sudo yum install -y java-11-amazon-corretto-devel
Verify Installation with the following command:
java -version
For more information, see Install Amazon Corretto 11 on RPM-Based Linux.
Install AWS SAM
To install AWS SAM, run the following command:
wget https://github.com/aws/aws-sam-cli/releases/latest/download/aws-sam-cli-linux-x86_64.zip unzip aws-sam-cli-linux-x86_64.zip -d sam-installation sudo ./sam-installation/install sam --version
For more information, see Installing the AWS SAM CLI.
Install Gradle
To install Gradle, run the following command:
wget https://services.gradle.org/distributions/gradle-7.6-bin.zip sudo mkdir /opt/gradle sudo unzip -d /opt/gradle gradle-7.6-bin.zip export PATH=$PATH:/opt/gradle/gradle-7.6/bin gradle -v
For more information, see Installing with a package manager on the Gradle website.
Note: You might need to export the PATH again after you reboot. To avoid this step, append the following line to the ~/.bashrc file:
export PATH=$PATH:/opt/gradle/gradle-7.6/bin
Create the project and files
To create the root folder for the SAM project, run the following command:
mkdir javaBasedCustomResource cd javaBasedCustomResource/
To create the necessary folders for your project, run the following command:
mkdir -p src/Function/src/main/java/
Create the project files through the Vim text editor. For more information, see the Vim website. Copy the following content to run in Vim:
vim template.yaml vim ./src/Function/build.gradle vim ./src/Function/src/main/java/Handler.java
Your project's structure resembles the following structural tree:
. └── javaBasedCustomResource ├── src │ └── Function │ ├── build.gradle │ └── src │ └── main │ └── java │ └── Handler.java └── template.yaml
See the following sections for the file templates to build your project.
template.yaml
Use the following template for the stack. This defines the Lambda function, log group, and CustomResource:
Transform: AWS::Serverless-2016-10-31 Resources: CustomResourceJavaBased: Type: AWS::CloudFormation::CustomResource Properties: ServiceToken: !Join ["", [ !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:", !Ref CustomResourceLambdaInJava]] DummyKey: DummyValue CustomResourceLambdaInJava: Type: AWS::Serverless::Function Properties: Description: !Sub - Stack ${AWS::StackName} Function ${ResourceName} - ResourceName: CustomResourceLambdaInJava CodeUri: src/Function Handler: Handler::handleRequest Runtime: java11 MemorySize: 3008 Timeout: 30 Tracing: Active CustomResourceLambdaInJavaLogGroup: Type: AWS::Logs::LogGroup DeletionPolicy: Retain Properties: LogGroupName: !Sub /aws/lambda/${CustomResourceLambdaInJava}
build.gradle
Use the following file template to facilitate your java build:
apply plugin: 'java' repositories { mavenCentral() } dependencies { implementation 'com.amazonaws:aws-lambda-java-core:1.2.2' implementation 'com.amazonaws:aws-lambda-java-events:3.11.0' implementation 'com.google.code.gson:gson:2.10' } task buildZip(type: Zip) { from compileJava from processResources into('lib') { from configurations.compileClasspath } } build.dependsOn buildZip
Handler.java
Lambda runs the following code. This sets the input event to a Map from which you can get the necessary endpoint URL to send back the response. Put all the needed parameters in a JSON string, and send an HTTP request to the endpoint:
import com.google.gson.Gson; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.*; import java.io.*; public class Handler { public String handleRequest(Object event) { /* Customer workload */ System.out.println("Dummy CustomResource Job"); /* sending signal back to CFN stack */ try { sendResponse(event); } catch (Exception e){ System.out.println("Got Exception!!"); e.printStackTrace(System.out); } return "JobFinished"; } public void sendResponse(Object event) throws IOException, InterruptedException { System.out.println("start sending signal"); Gson gson = new Gson(); String eventJson = gson.toJson(event); Map map = gson.fromJson(eventJson, Map.class); System.out.println("Request event: " + eventJson); /* Generate response parameters */ String putEndpoint = (String)map.get("ResponseURL"); String Status = "SUCCESS"; // "SUCCESS" or "FAILED" String Reason = "Dummy reason for Java based Custom Resource"; String PhysicalResourceId = "CustomResourcePhysicalID"; String StackId = (String)map.get("StackId"); String RequestId = (String)map.get("RequestId"); String LogicalResourceId = (String)map.get("LogicalResourceId"); /* Building response */ String responseJson = "{\"Status\":\"" + Status + "\",\"Reason\":\"" + Reason + "\",\"PhysicalResourceId\":\"" + PhysicalResourceId + "\",\"StackId\":\"" + StackId + "\",\"RequestId\":\"" + RequestId + "\",\"LogicalResourceId\":\"" + LogicalResourceId + "\",\"NoEcho\":false,\"Data\":{\"Key\":\"Value\"}}"; System.out.println("Response event: " + responseJson); var request = HttpRequest.newBuilder() .uri(URI.create(putEndpoint)) .header("Content-Type", "application/json") .PUT(HttpRequest.BodyPublishers.ofString(responseJson)) .build(); var client = HttpClient.newHttpClient(); /* Sending Response */ System.out.println("Sending Response to stack, response code: "); var response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.statusCode()); System.out.println(response.body()); System.out.println("Finish sending signal"); } }
Deploy the project
1. After you create your AWS SAM project, run following command under the root project folder javaBasedCustomResource:
sam build
2. To deploy the project, run the following command. This initiates a guide that prompts you to specify the the stack name and AWS Region where you want the resources to be created:
sam deploy --guided
This deploys a CloudFormation stack in your account with the name that you specified. The stack contains a Lambda function that runs the code from the previous steps. CloudFormation also creates the custom resource type AWS::CloudFormation::CustomResource in the same stack.
When CloudFormation creates AWS::CloudFormation::CustomResource, CloudFormation also invokes the above Lambda function. In response, the Lambda function sends a SUCCESS signal back to CloudFormation. This SUCCESS signal sends the resource to CreateComplete.
When you see the custom resource in the stack create successfully, this means that Lambda is also running successfully. Lambda returns the signal to the stack, and the java based custom resource initiates successfully.
You can check the Lambda logs for more details on metrics such as the request event and response event. You can modify the function code to specify your own tasks or create your own resources.
Relevant content
- asked a year agolg...
- Accepted Answerasked 2 months agolg...
- Accepted Answerasked 2 years agolg...
- AWS OFFICIALUpdated 2 years ago
- AWS OFFICIALUpdated 7 months ago