Skip to content

Commit

Permalink
Merge pull request quarkusio#14598 from patriot1burke/api-gateway-htt…
Browse files Browse the repository at this point in the history
…p-event-v2

Upgrade amazon-lambda-http to use API Gateway 2.0 http event
  • Loading branch information
patriot1burke authored Jan 28, 2021
2 parents 5389d76 + 16d78e8 commit 6cdd207
Show file tree
Hide file tree
Showing 48 changed files with 1,819 additions and 273 deletions.
10 changes: 10 additions & 0 deletions bom/application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1686,6 +1686,16 @@
<artifactId>quarkus-amazon-lambda-http-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-amazon-lambda-http-v1</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-amazon-lambda-http-v1-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-amazon-common</artifactId>
Expand Down
4 changes: 2 additions & 2 deletions devtools/cli/src/test/java/io/quarkus/cli/CliTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public void testAddListRemove() throws Exception {
// test list installable
execute("list", "--installable");
Assertions.assertEquals(CommandLine.ExitCode.OK, exitCode);
Assertions.assertFalse(screen.contains("quarkus-amazon-lambda-http"));
Assertions.assertFalse(screen.contains("quarkus-jackson"));
Assertions.assertTrue(screen.contains("quarkus-azure-functions-http"));

// test list search installable
Expand Down Expand Up @@ -192,7 +192,7 @@ public void testGradleAddListRemove() throws Exception {
// test list installable
execute("list", "--installable");
Assertions.assertEquals(CommandLine.ExitCode.OK, exitCode);
Assertions.assertFalse(screen.contains("quarkus-amazon-lambda-http"));
Assertions.assertFalse(screen.contains("quarkus-jackson"));
Assertions.assertTrue(screen.contains("quarkus-azure-functions-http"));

// test list search installable
Expand Down
106 changes: 26 additions & 80 deletions docs/src/main/asciidoc/amazon-lambda-http.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc
include::./attributes.adoc[]

The `quarkus-amazon-lambda-http` extension allows you to write microservices with RESTEasy (JAX-RS),
Undertow (servlet), Vert.x Web, or link:funqy-http[Funqy HTTP] and make these microservices deployable as an Amazon Lambda
Undertow (servlet), Vert.x Web, link:funqy-http[Funqy HTTP] or any other Quarkus HTTP API that runs on top of Vert.x Web
and deploy them to Amazon Lambda
using https://aws.amazon.com/api-gateway/[Amazon's API Gateway] and https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html[Amazon's SAM framework].

You can deploy your Lambda as a pure Java jar, or you can compile your project to a native image and deploy that for a smaller
Expand Down Expand Up @@ -50,6 +51,9 @@ mvn archetype:generate \
-DarchetypeVersion={quarkus-version}
----

NOTE: This extension has recently been upgraded to use API Gateway V2. If you need to still use V1 of the API Gateway
use the `quarkus-amazon-lambda-http-v1` extension instead.

== Build and Deploy

Build the project using maven.
Expand Down Expand Up @@ -107,116 +111,58 @@ http://127.0.0.1:3000/hello
In the console you'll see startup messages from the lambda. This particular deployment starts a JVM and loads your
lambda as pure Java.

If you want to deploy a native executable of your lambda, use a different yaml template that is provided in your
generated project:

[source,bash,subs=attributes+]
----
sam local start-api --template target/sam.native.yaml
----

== Deploy to AWS

There are a few steps to get your lambda running on AWS.

=== Package your deployment.

[source,bash,subs=attributes+]
----
sam package --template-file target/sam.jvm.yaml --output-template-file packaged.yaml --s3-bucket <YOUR_S3_BUCKET>
sam deploy -t target/sam.jvm.yaml -g
----

Type the simple name of your S3 bucket you created during. If you've built a native executable, replace
`sam.jvm.yaml` with `sam.native.yaml`.
Answer all the questions and your lambda will be deployed and the necessary hooks to the API Gateway will be set up. If
everything deploys successfully, the root URL of your microservice will be output to the console. Something like this:

=== Deploy your package

[source,bash,subs=attributes+]
----
sam deploy --template-file packaged.yaml --capabilities CAPABILITY_IAM --stack-name <YOUR_STACK_NAME>
Key LambdaHttpApi
Description URL for application
Value https://234asdf234as.execute-api.us-east-1.amazonaws.com/
----

The stack name can be anything you want.
The `Value` attribute is the root URL for your lambda. Copy it to your browser and add `hello` at the end.

=== Debugging AWS Deployment Problems

If `sam deploy`, run the `describe-stack-events` command
to get information about your deployment and what happened.

[source,bash,subs=attributes+]
----
aws cloudformation describe-stack-events --stack-name <YOUR_STACK_NAME>
----

One common issue that you may run across is that your S3 bucket has to be in the same region as Amazon Lambda.
Look for this error from `describe-stack-events` output:

[source, subs=attributes+]
----
Error occurred while GetObject. S3 Error Code: AuthorizationHeaderMalformed. S3 Error Message:
The authorization header is malformed; the region 'us-east-1' is wrong; expecting 'us-east-2'
(Service: AWSLambdaInternal; Status Code: 400; Error Code: InvalidParameterValueException;
Request ID: aefcf978-ad2a-4b53-9ffe-cea3fcd0f868)
----
[NOTE]
Responses for binary types will be automatically encoded with base64. This is different than the behavior using
`quarkus:dev` which will return the raw bytes. Amazon's API has additional restrictions requiring the base64 encoding.
In general, client code will automatically handle this encoding but in certain custom situations, you should be aware
you may need to manually manage that encoding.

The above error is stating that my S3 bucket should be in `us-east-2`, not `us-east-1`.
To fix this error you'll need to create an S3 bucket in that region and redo steps 1 and 2 from above.
== Deploying a native executable

Another annoying this is that if there is an error in deployment, you also have to completely delete
it before trying to deploy again:
To deploy a native executable, you must build it with Graal.

[source,bash,subs=attributes+]
----
aws cloudformation delete-stack --stack-name <YOUR_STACK_NAME>
./mvnw clean install -Dnative
----

== Execute your REST Lambda on AWS

To get the root URL for your service, type the following command and see the following output:
You can then test the executable locally with sam local

[source,bash,subs=attributes+]
----
aws cloudformation describe-stacks --stack-name <YOUR_STACK_NAME>
sam local start-api --template target/sam.native.yaml
----

It should give you something like the following output:
[source,subs=attributes+]
To deploy to AWS Lambda:
[source,bash,subs=attributes+]
----
{
"Stacks": [
{
"StackId": "arn:aws:cloudformation:us-east-1:502833056128:stack/QuarkusNativeRestExample2/b35b0200-f685-11e9-aaa0-0e8cd4caae34",
"DriftInformation": {
"StackDriftStatus": "NOT_CHECKED"
},
"Description": "AWS Serverless Quarkus HTTP - io.demo::rest-example",
"Tags": [],
"Outputs": [
{
"Description": "URL for application",
"ExportName": "RestExampleNativeApi",
"OutputKey": "RestExampleNativeApi",
"OutputValue": "https://234234234.execute-api.us-east-1.amazonaws.com/Prod/"
}
],
sam deploy -t target/sam.native.yaml -g
----

The `OutputValue` attribute is the root URL for your lambda. Copy it to your browser and add `hello` at the end.

[NOTE]
Responses for binary types will be automatically encoded with base64. This is different than the behavior using
`quarkus:dev` which will return the raw bytes. Amazon's API has additional restrictions requiring the base64 encoding.
In general, client code will automatically handle this encoding but in certain custom situations, you should be aware
you may need to manually manage that encoding.

== Examine the POM

There is nothing special about the POM other than the inclusion of the `quarkus-amazon-lambda-http` extension
as a dependencies. The extension automatically generates everything you might need for your lambda deployment.

NOTE: In previous versions of this extension you had to set up your pom or gradle
to zip up your executable for native deployments, but this is not the case anymore.

Also, at least in the generated maven archetype `pom.xml`, the `quarkus-resteasy`, `quarkus-vertx-web`, and `quarkus-undertow`
dependencies are all optional. Pick which http framework(s) you want to use (JAX-RS, Vertx Web, and/or Servlet) and
remove the other dependencies to shrink your deployment.
Expand Down Expand Up @@ -245,7 +191,7 @@ Another thing to note is that for pure Java lambda deployments, do not change th
----
Properties:
Handler: io.quarkus.amazon.lambda.runtime.QuarkusStreamHandler::handleRequest
Runtime: java8
Runtime: java11
----

This particular handler handles all the intricacies of integrating with the Quarkus runtime. So you must use that
Expand Down
51 changes: 51 additions & 0 deletions extensions/amazon-lambda-http-v1/deployment/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-amazon-lambda-http-v1-parent</artifactId>
<version>999-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

<artifactId>quarkus-amazon-lambda-http-v1-deployment</artifactId>
<name>Quarkus - Amazon Lambda HTTP V1 - Deployment</name>

<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-core-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx-http-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-amazon-lambda-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-amazon-lambda-http-v1</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-processor</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package io.quarkus.amazon.lambda.http.deployment;

import org.jboss.logging.Logger;

import io.quarkus.amazon.lambda.deployment.LambdaUtil;
import io.quarkus.amazon.lambda.deployment.ProvidedAmazonLambdaHandlerBuildItem;
import io.quarkus.amazon.lambda.http.LambdaHttpHandler;
import io.quarkus.amazon.lambda.http.model.AlbContext;
import io.quarkus.amazon.lambda.http.model.ApiGatewayAuthorizerContext;
import io.quarkus.amazon.lambda.http.model.ApiGatewayRequestIdentity;
import io.quarkus.amazon.lambda.http.model.AwsProxyRequest;
import io.quarkus.amazon.lambda.http.model.AwsProxyRequestContext;
import io.quarkus.amazon.lambda.http.model.AwsProxyResponse;
import io.quarkus.amazon.lambda.http.model.CognitoAuthorizerClaims;
import io.quarkus.amazon.lambda.http.model.ErrorModel;
import io.quarkus.amazon.lambda.http.model.Headers;
import io.quarkus.amazon.lambda.http.model.MultiValuedTreeMap;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.LaunchModeBuildItem;
import io.quarkus.deployment.builditem.SystemPropertyBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.pkg.builditem.ArtifactResultBuildItem;
import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem;
import io.quarkus.runtime.LaunchMode;
import io.quarkus.vertx.http.deployment.RequireVirtualHttpBuildItem;
import io.vertx.core.file.impl.FileResolver;

public class AmazonLambdaHttpProcessor {
private static final Logger log = Logger.getLogger(AmazonLambdaHttpProcessor.class);

@BuildStep
public RequireVirtualHttpBuildItem requestVirtualHttp(LaunchModeBuildItem launchMode) {
return launchMode.getLaunchMode() == LaunchMode.NORMAL ? RequireVirtualHttpBuildItem.MARKER : null;
}

@BuildStep
public ProvidedAmazonLambdaHandlerBuildItem setHandler() {
return new ProvidedAmazonLambdaHandlerBuildItem(LambdaHttpHandler.class, "AWS Lambda HTTP");
}

@BuildStep
public void registerReflectionClasses(BuildProducer<ReflectiveClassBuildItem> reflectiveClassBuildItemBuildProducer) {
reflectiveClassBuildItemBuildProducer
.produce(new ReflectiveClassBuildItem(true, true, true,
AlbContext.class,
ApiGatewayAuthorizerContext.class,
ApiGatewayRequestIdentity.class,
AwsProxyRequest.class,
AwsProxyRequestContext.class,
AwsProxyResponse.class,
CognitoAuthorizerClaims.class,
ErrorModel.class,
Headers.class,
MultiValuedTreeMap.class));
}

/**
* Lambda provides /tmp for temporary files. Set vertx cache dir
*/
@BuildStep
void setTempDir(BuildProducer<SystemPropertyBuildItem> systemProperty) {
systemProperty.produce(new SystemPropertyBuildItem(FileResolver.CACHE_DIR_BASE_PROP_NAME, "/tmp"));
}

@BuildStep
public void generateScripts(OutputTargetBuildItem target,
BuildProducer<ArtifactResultBuildItem> artifactResultProducer) throws Exception {
String lambdaName = LambdaUtil.artifactToLambda(target.getBaseName());

String output = LambdaUtil.copyResource("lambda/bootstrap-example.sh");
LambdaUtil.writeFile(target, "bootstrap-example.sh", output);

output = LambdaUtil.copyResource("http/sam.jvm.yaml")
.replace("${lambdaName}", lambdaName);
LambdaUtil.writeFile(target, "sam.jvm.yaml", output);

output = LambdaUtil.copyResource("http/sam.native.yaml")
.replace("${lambdaName}", lambdaName);
LambdaUtil.writeFile(target, "sam.native.yaml", output);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: AWS Serverless Quarkus HTTP - ${artifactId}
Globals:
Api:
EndpointConfiguration: REGIONAL
BinaryMediaTypes:
- "*/*"

Resources:
${lambdaName}:
Type: AWS::Serverless::Function
Properties:
Handler: io.quarkus.amazon.lambda.runtime.QuarkusStreamHandler::handleRequest
Runtime: java11
CodeUri: function.zip
MemorySize: 512
Policies: AWSLambdaBasicExecutionRole
Timeout: 15
Events:
GetResource:
Type: Api
Properties:
Path: /{proxy+}
Method: any

Outputs:
${lambdaName}Api:
Description: URL for application
Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/'
Export:
Name: ${lambdaName}Api
Loading

0 comments on commit 6cdd207

Please sign in to comment.