Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
January 2, 2022 08:55 pm GMT

JBang Quarkus AWS Lambda Terraform = <3

Have you ever wondered how can you utilize your Java skills in the serverless world?
If not then let me take you on a small trip where we will create AWS Lambda functions with Java, and yes with JBang.

Prerequisites:

I would not like to go through how these tools should be installed, I assume these things are a piece of cake.

My versions
 jbang version0.86.0 aws --versionaws-cli/1.22.5 Python/3.9.5 Linux/5.15.8-76051508-generic botocore/1.23.5 terraform -vTerraform v1.0.0on linux_amd64

JBang

JBang is a powerful tool which lets you create .java files with your requested dependencies, and with different commands you will be able to build and export .jar files and native binaries.

Okay okay, we have Maven and Gradle, why would I need this?

For my answer for this is the following: If you really want to code just a small app with some dependencies rather than creating and maintaining a project with a pom.xml or a gradle.build could be overkill, like in the following use case where we are going to create a Lambda function.

My motivation

I have attended projects where all the Lambda functions were written in Python, and I'm not a Python developer, of course another programming language, can be learned easily, but with deadlines on our back, if the team is more of a Java team, then writing Lambda functions in Java makes more sense.

What I really like in the Python or JavaScript based Lambda functions are their "lightness", the authors of the functions created a small .py or .js file, and they could deploy it and invoke it and of course they have the online code editor, with Java we won't have that feature, but we can utilize our Java knowledge. Of course dependency management should happen if we need external dependencies, with Python I know it is relatively easy, and of course with Java too, Maven and Gradle are beautiful tools, but I think they are overkill for smaller functions.

I really wanted to have almost the same "workflow" with Java, just one .java file per function that can have external dependencies (like Quarkus that we are also going to use because it has a really nice integration with AWS Lambda as well) listed somewhere in the .java source file as well and can be built by anybody who has the jbang binary on their workstation.

Our first JBang "script"

After JBang got installed we can start working with that, let's create our very first script with the following command:

 jbang init hellojbang.java[jbang] File initialized. You can now run it with 'jbang hellojbang.java' or edit it using 'jbang edit --open=[editor] hellojbang.java' where [editor] is your editor or IDE, e.g. 'idea'

After the file is created we have a "few" options to edit it. We can use the command it outputs with our installed IDE (IDEA,VSCode): jbang edit --open=idea hellojbang.java.
At first glance it could be a bit "weird", I was talking about having no build tool involved in the flow, but we see a build.gradle file, but do not worry, this is just a small helper project that was created for it, to have IDE support, as you can see the whole project sits in the ~/.jbang/cache folder and a symbolic link was created for it.

For IntelliJ IDEA JBang has a really nice plugin, really young, few weeks old but can do the work: https://plugins.jetbrains.com/plugin/18257-jbang in this case you do not have to use the edit command, because IDEA will have a feature to download sync all dependencies and have code completion.

If we open the file we will see the following:

///usr/bin/env jbang "$0" "$@" ; exit $?import static java.lang.System.*;public class hellojbang {    public static void main(String... args) {        out.println("Hello World");    }}

We can run the .java file with the following commands:

  • jbang hellojbang.java
  • jbang run hellojbang.java
  • ./hellojbang.java

The output will be the following every time:

 ./hellojbang.java [jbang] Building jar...Hello World

On the first run JBang creates a .jar file within its cache folder and it runs it, if codes has no changes compared to earlier run then it will not build it again.

Configuring dependencies and Java version

JBang uses // based directives to configure the dependencies for the application, and other things as well.
Let's see how we can add some dependencies and set the Java version to 11, because with the AWS Lambda we will only have a Java 11 runtime environment.

We can add dependencies with the //DEPS <gav> directive and we can set the Java version to 11 with //JAVA 11 directive

///usr/bin/env jbang "$0" "$@" ; exit $?//JAVA 11//DEPS org.apache.commons:commons-lang3:3.12.0import org.apache.commons.lang3.StringUtils;import static java.lang.System.*;public class hellojbang {    public static void main(String... args) {        out.println(StringUtils.abbreviate("Hello World", 4));    }}

Building and running the script and the output will be:

 jbang hellojbang.java[jbang] Building jar...H...

Nice, we added a dependency and set the Java version to 11. We can add unlimited amount of dependencies and we can use BOMs as well.
That was a brief introduction to JBang and now let's see the AWS stuff.

Quarkus & JBang & AWS Lambda & Terraform

Quarkus & JBang

Create a new .java file where we can write out Lambda function code.

 jbang init AwsLambdaFunction.java[jbang] File initialized. You can now run it with 'jbang AwsLambdaFunction.java' or edit it using 'jbang edit --open=[editor] AwsLambdaFunction.java' where [editor] is your editor or IDE, e.g. 'idea'

Open the file within our favourite editor: jbang edit --open=idea AwsLambdaFunction.java and add the following dependencies:

//DEPS io.quarkus:quarkus-bom:2.6.0.Final@pom//DEPS io.quarkus:quarkus-amazon-lambda

With that we state that we would like to use Quarkus at the "newest" version: 2.6.0 and we are adding a new dependency to the "project" as well: io.quarkus:quarkus-amazon-lambda. We don't have to provide the version number, JBang is smart enough to have this information from the BOM specified above it.

If we want to create a Lambda function with Quarkus we have to implement the com.amazonaws.services.lambda.runtime.RequestHandler interface by implementing the handleRequest method.

///usr/bin/env jbang "$0" "$@" ; exit $?//JAVA 11//DEPS io.quarkus:quarkus-bom:2.6.0.Final@pom//DEPS io.quarkus:quarkus-amazon-lambda//DEPS org.projectlombok:lombok:1.18.22//JAVA_OPTIONS -Djava.util.logging.manager=org.jboss.logmanager.LogManagerimport com.amazonaws.services.lambda.runtime.Context;import com.amazonaws.services.lambda.runtime.RequestHandler;import lombok.Builder;import lombok.Data;import org.jboss.logging.Logger;public class AwsLambdaFunction implements RequestHandler<LambdaInput, LambdaOutput> {    public static final Logger LOG = Logger.getLogger("AwsLambdaFunction");    public AwsLambdaFunction() {    }    @Override    public LambdaOutput handleRequest(LambdaInput input, Context context) {        LOG.info("Hello from Lambda: " + input);        return LambdaOutput.builder()                .result("Incoming text: " + input.getInput())                .build();    }}@Dataclass LambdaInput {    private String input;}@Data@Builderclass LambdaOutput {    private String result;}

In the "final" code snippet we can some new things:

  • //DEPS org.projectlombok:lombok:1.18.22 - Lombok, which is here to make the POJO classes thinner in the code.
  • //JAVA_OPTIONS -Djava.util.logging.manager=org.jboss.logmanager.LogManager - We would like to log, in this case we need a logger configuration.
  • We must have a public no-args constructor.
  • POJOs should be conventional Beans, with no-arg constructors and with getter/setter pairs.
 jbang AwsLambdaFunction.java[jbang] Resolving dependencies...[jbang] Artifacts used for dependency management:         io.quarkus:quarkus-bom:pom:2.6.0.Final[jbang] io.quarkus:quarkus-amazon-lambda         org.projectlombok:lombok:jar:1.18.22Done[jbang] Dependencies resolved[jbang] Building jar...[jbang] Post build with io.quarkus.launcher.JBangIntegrationJan 02, 2022 9:14:53 PM org.jboss.threads.Version <clinit>INFO: JBoss Threads version 3.4.2.FinalJan 02, 2022 9:14:53 PM io.quarkus.deployment.QuarkusAugmentor runINFO: Quarkus augmentation completed in 610ms__  ____  __  _____   ___  __ ____  ______  --/ __ \/ / / / _ | / _ \/ //_/ / / / __/  -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   --\___\_\____/_/ |_/_/|_/_/|_|\____/___/   2022-01-02 21:14:54,510 INFO  [io.quarkus] (main) Quarkus 2.6.0.Final on JVM started in 0.428s. 2022-01-02 21:14:54,515 INFO  [io.quarkus] (main) Profile prod activated. 2022-01-02 21:14:54,516 INFO  [io.quarkus] (main) Installed features: [amazon-lambda, cdi]

It means basically our code is using Quarkus and we are "almost done". Of course it would be nice to test it, right now we are not going to write unit tests for it, we would be able to, lets cover that topic in another time, right now just utilize Quarkus's dev mode with the following command:

 jbang -Dquarkus.dev AwsLambdaFunction.java[jbang] Building jar...[jbang] Post build with io.quarkus.launcher.JBangIntegration2022-01-02 21:17:08,318 INFO  [io.qua.ama.lam.run.MockEventServer] (build-10) Mock Lambda Event Server Started__  ____  __  _____   ___  __ ____  ______  --/ __ \/ / / / _ | / _ \/ //_/ / / / __/  -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   --\___\_\____/_/ |_/_/|_/_/|_|\____/___/   2022-01-02 21:17:08,647 INFO  [io.qua.ama.lam.run.AbstractLambdaPollLoop] (Lambda Thread (DEVELOPMENT)) Listening on: http://localhost:8080/_lambda_/2018-06-01/runtime/invocation/next2022-01-02 21:17:08,654 INFO  [io.quarkus] (Quarkus Main Thread) Quarkus 2.6.0.Final on JVM started in 1.135s. 2022-01-02 21:17:08,659 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.2022-01-02 21:17:08,659 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [amazon-lambda, cdi]--Tests pausedPress [r] to resume testing, [o] Toggle test output, [h] for more options>

Using the dev mode, Quarkus will start a mock HTTP event server so we can use curl or other tools to invoke an HTTP endpoint where we can POST our input object, and then we can examine the result/response as well:

 curl -X POST --location "http://localhost:8080" \    -H "Content-Type: application/json" \    -d "{          \"input\": \"Hello World\"        }"{"result":"Incoming text: Hello World"}%  

By the way, using Quarkus's dev mode lets you change the code "on-the-fly", and on the next invocation it will rebuild automatically. You do not have to build it every time by yourself.

AWS Lambda & Terraform

Make sure the AWS CLI is configured: https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html

Let's deploy our code to AWS with Terraform.

# Terraform basic configurationterraform {  required_version = ">= 1.0.0"  required_providers {    aws     = "~> 3.70.0"    local   = "2.1.0"  }}# Set AWS region to eu-central-1 -> Frankfurtprovider "aws" {  region = "eu-central-1"}# We have to create a role for the Lambda function, it is mandatory.resource "aws_iam_role" "iam_for_lambda" {  name = "iam_for_lambda_function"  assume_role_policy = <<EOF{  "Version": "2012-10-17",  "Statement": [    {      "Action": "sts:AssumeRole",      "Principal": {        "Service": "lambda.amazonaws.com"      },      "Effect": "Allow",      "Sid": ""    }  ]}EOF}# We have to somehow create the jar file that we will deploy, we are going to use the "local-exec" provision feature for it.# First we will build the .java file, then we have to export it, export means we are copying it from the jbang cache to the current working directory# After that we have to update the jar file, we have to move the exported "lib" folder to the jar file, we have to bundle all dependencies that we are relaying on.resource "local_file" "jar_file" {  filename       = "AwsLambdaFunction.jar"  content_base64 = filebase64sha256("AwsLambdaFunction.java")  provisioner "local-exec" {    command = "jbang build --fresh AwsLambdaFunction.java && jbang export portable --fresh --force AwsLambdaFunction.java && jar uf AwsLambdaFunction.jar lib/"  }}# Lambda function we want to create and invoke.resource "aws_lambda_function" "function" {  filename         = local_file.jar_file.filename  source_code_hash = local_file.jar_file.content_base64  function_name    = "AwsLambdaFunction"  role             = aws_iam_role.iam_for_lambda.arn  #Handler method must be io.quarkus.amazon.lambda.runtime.QuarkusStreamHandler::handleRequest  handler          = "io.quarkus.amazon.lambda.runtime.QuarkusStreamHandler::handleRequest"  depends_on = [local_file.jar_file]  runtime = "java11"  memory_size = 256}

Let's run the following commands:

First we have to call the terraform init, it will initialize the terraform state, and after that we can call terraform plan or terraform apply. plan will just only show what it would do if apply would be called.
After calling terraform apply we have to write yes when it asks for approval.

 terraform initInitializing the backend...Initializing provider plugins...- Finding hashicorp/aws versions matching "~> 3.70.0"...- Finding hashicorp/local versions matching "2.1.0"...- Installing hashicorp/aws v3.70.0...- Installed hashicorp/aws v3.70.0 (signed by HashiCorp)- Installing hashicorp/local v2.1.0...- Installed hashicorp/local v2.1.0 (signed by HashiCorp)Terraform has created a lock file .terraform.lock.hcl to record the providerselections it made above. Include this file in your version control repositoryso that Terraform can guarantee to make the same selections by default whenyou run "terraform init" in the future.Terraform has been successfully initialized!You may now begin working with Terraform. Try running "terraform plan" to seeany changes that are required for your infrastructure. All Terraform commandsshould now work.If you ever set or change modules or backend configuration for Terraform,rerun this command to reinitialize your working directory. If you forget, othercommands will detect it and remind you to do so if necessary.... terraform applyTerraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:  + createTerraform will perform the following actions:  # aws_iam_role.iam_for_lambda will be created  + resource "aws_iam_role" "iam_for_lambda" {      + arn                   = (known after apply)      + assume_role_policy    = jsonencode(            {              + Statement = [                  + {                      + Action    = "sts:AssumeRole"                      + Effect    = "Allow"                      + Principal = {                          + Service = "lambda.amazonaws.com"                        }                      + Sid       = ""                    },                ]              + Version   = "2012-10-17"            }        )      + create_date           = (known after apply)      + force_detach_policies = false      + id                    = (known after apply)      + managed_policy_arns   = (known after apply)      + max_session_duration  = 3600      + name                  = "iam_for_lambda"      + name_prefix           = (known after apply)      + path                  = "/"      + tags_all              = (known after apply)      + unique_id             = (known after apply)      + inline_policy {          + name   = (known after apply)          + policy = (known after apply)        }    }  # aws_lambda_function.function will be created  + resource "aws_lambda_function" "function" {      + architectures                  = (known after apply)      + arn                            = (known after apply)      + filename                       = "AwsLambdaFunction.jar"      + function_name                  = "AwsLambdaFunction"      + handler                        = "io.quarkus.amazon.lambda.runtime.QuarkusStreamHandler::handleRequest"      + id                             = (known after apply)      + invoke_arn                     = (known after apply)      + last_modified                  = (known after apply)      + memory_size                    = 256      + package_type                   = "Zip"      + publish                        = false      + qualified_arn                  = (known after apply)      + reserved_concurrent_executions = -1      + role                           = (known after apply)      + runtime                        = "java11"      + signing_job_arn                = (known after apply)      + signing_profile_version_arn    = (known after apply)      + source_code_hash               = "zxCVmQSXmb7Zf3EPLyKKVgL5Tv61WGLArpHz8QSum2c="      + source_code_size               = (known after apply)      + tags_all                       = (known after apply)      + timeout                        = 3      + version                        = (known after apply)      + tracing_config {          + mode = (known after apply)        }    }  # local_file.jar_file will be created  + resource "local_file" "jar_file" {      + content_base64       = "zxCVmQSXmb7Zf3EPLyKKVgL5Tv61WGLArpHz8QSum2c="      + directory_permission = "0777"      + file_permission      = "0777"      + filename             = "AwsLambdaFunction.jar"      + id                   = (known after apply)    }Plan: 3 to add, 0 to change, 0 to destroy.Do you want to perform these actions?  Terraform will perform the actions described above.  Only 'yes' will be accepted to approve.  Enter a value: 

After entring yes and pressing Enter we should see the following output:

local_file.jar_file: Creating...local_file.jar_file: Provisioning with 'local-exec'...local_file.jar_file (local-exec): Executing: ["/bin/sh" "-c" "jbang build --fresh AwsLambdaFunction.java && jbang export portable --fresh --force AwsLambdaFunction.java && jar uf AwsLambdaFunction.jar lib/"]local_file.jar_file (local-exec): [jbang] Resolving dependencies...local_file.jar_file (local-exec): [jbang] Artifacts used for dependency management:local_file.jar_file (local-exec):          io.quarkus:quarkus-bom:pom:2.6.0.Finallocal_file.jar_file (local-exec): [jbang] io.quarkus:quarkus-amazon-lambdalocal_file.jar_file (local-exec):          org.projectlombok:lombok:jar:1.18.22local_file.jar_file (local-exec): Donelocal_file.jar_file (local-exec): [jbang] Dependencies resolvedlocal_file.jar_file (local-exec): [jbang] Building jar...aws_iam_role.iam_for_lambda: Creating...local_file.jar_file (local-exec): [jbang] Post build with io.quarkus.launcher.JBangIntegrationlocal_file.jar_file (local-exec): Jan 02, 2022 9:40:29 PM org.jboss.threads.Version <clinit>local_file.jar_file (local-exec): INFO: JBoss Threads version 3.4.2.Finallocal_file.jar_file (local-exec): Jan 02, 2022 9:40:30 PM io.quarkus.deployment.QuarkusAugmentor runlocal_file.jar_file (local-exec): INFO: Quarkus augmentation completed in 652msaws_iam_role.iam_for_lambda: Creation complete after 2s [id=iam_for_lambda_function]local_file.jar_file (local-exec): [jbang] Resolving dependencies...local_file.jar_file (local-exec): [jbang] Artifacts used for dependency management:local_file.jar_file (local-exec):          io.quarkus:quarkus-bom:pom:2.6.0.Finallocal_file.jar_file (local-exec): [jbang] io.quarkus:quarkus-amazon-lambdalocal_file.jar_file (local-exec):          org.projectlombok:lombok:jar:1.18.22local_file.jar_file (local-exec): Donelocal_file.jar_file (local-exec): [jbang] Dependencies resolvedlocal_file.jar_file (local-exec): [jbang] Building jar...local_file.jar_file (local-exec): [jbang] Post build with io.quarkus.launcher.JBangIntegrationlocal_file.jar_file (local-exec): Jan 02, 2022 9:40:33 PM org.jboss.threads.Version <clinit>local_file.jar_file (local-exec): INFO: JBoss Threads version 3.4.2.Finallocal_file.jar_file (local-exec): Jan 02, 2022 9:40:34 PM io.quarkus.deployment.QuarkusAugmentor runlocal_file.jar_file (local-exec): INFO: Quarkus augmentation completed in 705mslocal_file.jar_file (local-exec): [jbang] Updating jar manifestlocal_file.jar_file (local-exec): [jbang] Exported to /media/nandi/Data/VCS/GIT/jbang-terraform-aws/devto/AwsLambdaFunction.jarlocal_file.jar_file: Creation complete after 8s [id=352a94713061363fa798146c96e188a5dd35a975]aws_lambda_function.function: Creating...aws_lambda_function.function: Creation complete after 8s [id=AwsLambdaFunction]Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

If you want to test it from the AWS console you can do it here.

If you want to use the AWS CLI invoke the following command:

echo "{\"input\": \"Hello World from AWS Lambda\"}" > payload.jsonaws lambda invoke response.txt --function-name AwsLambdaFunction --log-type Tail --output text --query 'LogResult' --payload file://payload.json | base64 --decode

The output should look like this (will be different for you, date-time and IDs):

START RequestId: b58e9171-4d9c-4d92-8056-aa7e20317619 Version: $LATEST2022-01-02 20:45:51,864 INFO  [RequestHandlerExample] (main) Hello from Lambda: LambdaInput(input=Hello World from AWS Lambda)END RequestId: b58e9171-4d9c-4d92-8056-aa7e20317619REPORT RequestId: b58e9171-4d9c-4d92-8056-aa7e20317619  Duration: 1.47 ms       Billed Duration: 2 ms   Memory Size: 256 MB     Max Memory Used: 118 MB 

If we make any changes to our .java file and we want to deploy it to AWS, we just have to run terraform to do the heavy lifting for us.

Outro

It is a quick and brief article on how to create and deploy Java based functions to AWS Lambda using JBang and Terraform. I really like all the used technologies here.

One thing before I close the article: Quarkus is NOT a mandatory framework to use, I just used it because I really love working with that, and if the function would need database handling libs or would like to use dependency injection then we would be able to just add more and more dependencies to it and use it. We just barely touched the topic.

If you would like to learn more please check out the following sites:

Upcoming

I'm planning to make new articles about exploring AWS Lambda triggers like SQS, S3, SNS. Stay tuned!

Cover (Photo by Gbor Molnr): https://unsplash.com/photos/Y7ufx8R8PM0


Original Link: https://dev.to/nandorholozsnyak/jbang-quarkus-aws-lambda-terraform-3-4ahk

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To