diff --git a/.github/workflows/ci-actions.yml b/.github/workflows/ci-actions.yml
index ff18b577d9506..328fa08976a0c 100644
--- a/.github/workflows/ci-actions.yml
+++ b/.github/workflows/ci-actions.yml
@@ -165,13 +165,15 @@ jobs:
amazonServices:
image: localstack/localstack:0.11.1
env:
- SERVICES: s3,dynamodb,sns,sqs
+ SERVICES: s3,dynamodb,sns,sqs,kms,ses
START_WEB: 0
ports:
- 127.0.0.1:8000:4569
- 127.0.0.1:8008:4572
- 127.0.0.1:8009:4575
- 127.0.0.1:8010:4576
+ - 127.0.0.1:8011:4599
+ - 127.0.0.1:8012:4566
steps:
- name: Start mysql
@@ -356,7 +358,7 @@ jobs:
reactive-pg-client
- category: Amazon
amazonServices: "true"
- timeout: 25
+ timeout: 35
test-modules: >
amazon-services
amazon-lambda
@@ -488,7 +490,7 @@ jobs:
if: matrix.mssql
- name: Amazon Services
run: |
- docker run --rm --publish 8000:4569 --publish 8008:4572 --publish 8009:4575 --publish 8010:4576 --name build-amazon-service-clients -e SERVICES=s3,dynamodb,sns,sqs -e START_WEB=0 \
+ docker run --rm --publish 8000:4569 --publish 8008:4572 --publish 8009:4575 --publish 8010:4576 --publish 8011:4599 --publish 8012:4566 --name build-amazon-service-clients -e SERVICES=s3,dynamodb,sns,sqs,kms,ses -e START_WEB=0 \
-d localstack/localstack:0.11.1
if: matrix.amazonServices
- name: Neo4j Service
diff --git a/bom/deployment/pom.xml b/bom/deployment/pom.xml
index dcc25ef267713..4e20f542e26dd 100644
--- a/bom/deployment/pom.xml
+++ b/bom/deployment/pom.xml
@@ -593,6 +593,16 @@
quarkus-amazon-sqs-deployment
${project.version}
+
+ io.quarkus
+ quarkus-amazon-ses-deployment
+ ${project.version}
+
+
+ io.quarkus
+ quarkus-amazon-kms-deployment
+ ${project.version}
+
io.quarkus
quarkus-amazon-lambda-http-deployment
diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml
index 982beb0611937..7ba7c5dc0be8f 100644
--- a/bom/runtime/pom.xml
+++ b/bom/runtime/pom.xml
@@ -864,6 +864,16 @@
quarkus-amazon-sqs
${project.version}
+
+ io.quarkus
+ quarkus-amazon-ses
+ ${project.version}
+
+
+ io.quarkus
+ quarkus-amazon-kms
+ ${project.version}
+
io.quarkus
quarkus-amazon-alexa
@@ -3000,6 +3010,16 @@
sqs
${awssdk.version}
+
+ software.amazon.awssdk
+ ses
+ ${awssdk.version}
+
+
+ software.amazon.awssdk
+ kms
+ ${awssdk.version}
+
software.amazon.awssdk
netty-nio-client
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java
index 5a39d8841bdc5..f5f6ffea4bd05 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java
@@ -15,6 +15,8 @@ public final class FeatureBuildItem extends MultiBuildItem {
public static final String AMAZON_S3 = "amazon-s3";
public static final String AMAZON_SNS = "amazon-sns";
public static final String AMAZON_SQS = "amazon-sqs";
+ public static final String AMAZON_SES = "amazon-ses";
+ public static final String AMAZON_KMS = "amazon-kms";
public static final String ARTEMIS_CORE = "artemis-core";
public static final String ARTEMIS_JMS = "artemis-jms";
public static final String CACHE = "cache";
diff --git a/docs/src/main/asciidoc/amazon-kms.adoc b/docs/src/main/asciidoc/amazon-kms.adoc
new file mode 100644
index 0000000000000..a73c2bd3df15e
--- /dev/null
+++ b/docs/src/main/asciidoc/amazon-kms.adoc
@@ -0,0 +1,294 @@
+////
+This guide is maintained in the main Quarkus repository
+and pull requests should be submitted there:
+https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc
+////
+= Quarkus - Amazon KMS Client
+:extension-status: preview
+
+include::./attributes.adoc[]
+
+Amazon Key Management Service (KMS) is a service that allows you to create and control the keys used to encrypt or digitally sign your data.
+Using KMS, you can create and manage cryptographic keys and control their use across a wide range of AWS services and in your application.
+
+You can find more information about KMS at https://aws.amazon.com/kms/[the Amazon KMS website].
+
+NOTE: The KMS extension is based on https://docs.aws.amazon.com/sdk-for-java/v2/developer-guide/welcome.html[AWS Java SDK 2.x].
+It's a major rewrite of the 1.x code base that offers two programming models (Blocking & Async).
+
+include::./status-include.adoc[]
+
+The Quarkus extension supports two programming models:
+
+* Blocking access using URL Connection HTTP client (by default) or the Apache HTTP Client
+* https://docs.aws.amazon.com/sdk-for-java/v2/developer-guide/basics-async.html[Asynchronous programming] based on JDK's `CompletableFuture` objects and the Netty HTTP client.
+
+In this guide, we see how you can get your REST services to use KMS locally and on AWS.
+
+== Prerequisites
+
+To complete this guide, you need:
+
+* JDK 1.8+ installed with `JAVA_HOME` configured appropriately
+* an IDE
+* Apache Maven {maven-version}
+* An AWS Account to access the KMS service
+* Docker for your system to run KMS locally for testing purposes
+
+=== Set up KMS locally
+
+The easiest way to start working with KMS is to run a local instance as a container.
+
+[source,shell,subs="verbatim,attributes"]
+----
+docker run --rm --name local-kms 8011:4599 -e SERVICES=kms -e START_WEB=0 -d localstack/localstack:0.11.1
+----
+This starts a KMS instance that is accessible on port `8011`.
+
+Create an AWS profile for your local instance using AWS CLI:
+[source,shell,subs="verbatim,attributes"]
+----
+$ aws configure --profile localstack
+AWS Access Key ID [None]: test-key
+AWS Secret Access Key [None]: test-secret
+Default region name [None]: us-east-1
+Default output format [None]:
+----
+
+=== Create a KMS master key
+
+Create a KMS master key queue using AWS CLI and store in `MASTER_KEY_ARN` environment variable.
+
+[source,shell,subs="verbatim,attributes"]
+----
+MASTER_KEY_ARN=`aws kms create-key --profile localstack --endpoint-url=http://localhost:8011 | cut -f3`
+----
+Generate a key data as 256-bit symnmetric key (AES 256)
+[source,shell,subs="verbatim,attributes"]
+----
+aws kms generate-data-key --key-id $MASTER_KEY_ARN --key-spec AES_256 --profile localstack --endpoint-url=http://localhost:8011
+----
+
+Or, if you want to use your AWS account create a key using your default profile
+[source,shell,subs="verbatim,attributes"]
+----
+MASTER_KEY_ARN=`aws kms create-key | cut -f3`
+aws kms generate-data-key --key-id $MASTER_KEY_ARN --key-spec AES_256
+----
+
+== Solution
+The application built here allows to encrypt and decrypt text messages using a master key created on AWS KMS.
+
+We recommend that you follow the instructions in the next sections and create the application step by step.
+However, you can go right to the completed example.
+
+Clone the Git repository: `git clone {quickstarts-clone-url}`, or download an {quickstarts-archive-url}[archive].
+
+The solution is located in the `amazon-kms-quickstart` {quickstarts-tree-url}/amazon-kms-quickstart[directory].
+
+== Creating the Maven project
+
+First, we need a new project. Create a new project with the following command:
+
+[source,shell,subs=attributes+]
+----
+mvn io.quarkus:quarkus-maven-plugin:{quarkus-version}:create \
+ -DprojectGroupId=org.acme \
+ -DprojectArtifactId=amazon-kms-quickstart \
+ -DclassName="org.acme.kms.QuarkusKmsSyncResource" \
+ -Dpath="/sync" \
+ -Dextensions="resteasy-jsonb,amazon-kms,resteasy-mutiny"
+cd amazon-kms-quickstart
+----
+
+This command generates a Maven structure importing the RESTEasy/JAX-RS, Mutiny and Amazon KMS Client extensions.
+After this, the `amazon-kms` extension has been added to your `pom.xml` as well as the Mutiny support for RESTEasy.
+
+== Creating JSON REST service
+
+In this example, we will create an application that allows to encrypt and decrypt text message provided in the request.
+The example application will demonstrate the two programming models supported by the extension.
+
+Lets create a `org.acme.kms.QuarkusKmsSyncResource` that will provide an API to encrypt and decrypt message using the synchronous client.
+
+[source,java]
+----
+package org.acme.kms;
+
+import javax.inject.Inject;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import org.apache.commons.codec.binary.Base64;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import software.amazon.awssdk.core.SdkBytes;
+import software.amazon.awssdk.services.kms.KmsClient;
+import software.amazon.awssdk.services.kms.model.DecryptResponse;
+
+@Path("/sync")
+@Produces(MediaType.TEXT_PLAIN)
+@Consumes(MediaType.TEXT_PLAIN)
+public class QuarkusKmsSyncResource {
+
+ @Inject
+ KmsClient kms;
+
+ @ConfigProperty(name = "key.arn")
+ String keyArn;
+
+ @POST
+ @Path("/encrypt")
+ public String encrypt(String data) {
+ SdkBytes encryptedBytes = kms.encrypt(req -> req.keyId(keyArn).plaintext(SdkBytes.fromUtf8String(data))).ciphertextBlob();
+
+ return Base64.encodeBase64String(encryptedBytes.asByteArray());
+ }
+
+ @POST
+ @Path("/decrypt")
+ public String decrypt(String data) {
+ SdkBytes encryptedData = SdkBytes.fromByteArray(Base64.decodeBase64(data.getBytes()));
+ DecryptResponse decrypted = kms.decrypt(req -> req.keyId(keyArn).ciphertextBlob(encryptedData));
+
+ return decrypted.plaintext().asUtf8String();
+ }
+}
+----
+An encrypted message is in the form of a bytes array. To return it to the user we need to encode it as Base64 string in the `encrypt` endpoint.
+On the `decrypt` endpoint we need to decode from the Base64 string back to the bytes array before sending it out to the KMS client.
+
+== Configuring KMS clients
+
+Both KMS clients (sync and async) are configurable via the `application.properties` file that can be provided in the `src/main/resources` directory.
+Additionally, you need to add to the classpath a proper implementation of the sync client. By default the extension uses the URL connection HTTP client, so
+you need to add a URL connection client dependency to the `pom.xml` file:
+
+[source,xml]
+----
+
+ software.amazon.awssdk
+ url-connection-client
+
+----
+
+If you want to use Apache HTTP client instead, configure it as follows:
+[source,properties]
+----
+quarkus.kms.sync-client.type=apache
+----
+
+And add the following dependency to the application `pom.xml`:
+[source,xml]
+----
+
+ software.amazon.awssdk
+ apache-client
+
+----
+
+If you're going to use a local KMS instance, configure it as follows:
+
+[source,properties]
+----
+quarkus.kms.endpoint-override=http://localhost:8011
+
+quarkus.kms.aws.region=us-east-1
+quarkus.kms.aws.credentials.type=static
+quarkus.kms.aws.credentials.static-provider.access-key-id=test-key
+quarkus.kms.aws.credentials.static-provider.secret-access-key=test-secret
+----
+
+- `quarkus.kms.aws.region` - It's required by the client, but since you're using a local KMS instance use `us-east-1` as it's a default region of localstack's KMS.
+- `quarkus.kms.aws.credentials.type` - Set `static` credentials provider with any values for `access-key-id` and `secret-access-key`
+- `quarkus.kms.endpoint-override` - Override the KMS client to use a local instance instead of an AWS service
+
+If you want to work with an AWS account, you can simply remove or comment out all Amazon KMS related properties. By default, the KMS client extension
+will use the `default` credentials provider chain that looks for credentials in this order:
+- Java System Properties - `aws.accessKeyId` and `aws.secretKey`
+* Environment Variables - `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`
+* Credential profiles file at the default location (`~/.aws/credentials`) shared by all AWS SDKs and the AWS CLI
+* Credentials delivered through the Amazon EC2 container service if the `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` environment variable is set and the security manager has permission to access the variable,
+* Instance profile credentials delivered through the Amazon EC2 metadata service
+
+And the region from your AWS CLI profile will be used.
+
+== Next steps
+
+=== Packaging
+
+Packaging your application is as simple as `./mvnw clean package`.
+It can be run with `java -Dkey.arn=$MASTER_KEY_ARN -jar target/amazon-kms-quickstart-1.0-SNAPSHOT-runner.jar`.
+
+With GraalVM installed, you can also create a native executable binary: `./mvnw clean package -Dnative`.
+Depending on your system, that will take some time.
+
+=== Going asynchronous
+
+Thanks to the AWS SDK v2.x used by the Quarkus extension, you can use the asynchronous programming model out of the box.
+
+Create a `org.acme.kms.QuarkusKmsAsyncResource` REST resource that will be similar to our `QuarkusKmsSyncResource` but using an asynchronous programming model.
+
+[source,java]
+----
+package org.acme.kms;
+
+import io.smallrye.mutiny.Uni;
+import javax.inject.Inject;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import org.apache.commons.codec.binary.Base64;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import software.amazon.awssdk.core.SdkBytes;
+import software.amazon.awssdk.services.kms.KmsAsyncClient;
+import software.amazon.awssdk.services.kms.model.DecryptResponse;
+import software.amazon.awssdk.services.kms.model.EncryptResponse;
+
+@Path("/async")
+@Produces(MediaType.TEXT_PLAIN)
+@Consumes(MediaType.TEXT_PLAIN)
+public class QuarkusKmsAsyncResource {
+
+ @Inject
+ KmsAsyncClient kms;
+
+ @ConfigProperty(name = "key.arn")
+ String keyArn;
+
+ @POST
+ @Path("/encrypt")
+ public Uni encrypt(String data) {
+ return Uni.createFrom().completionStage(kms.encrypt(req -> req.keyId(keyArn).plaintext(SdkBytes.fromUtf8String(data))))
+ .onItem().apply(EncryptResponse::ciphertextBlob)
+ .onItem().apply(blob -> Base64.encodeBase64String(blob.asByteArray()));
+ }
+
+ @POST
+ @Path("/decrypt")
+ public Uni decrypt(String data) {
+ return Uni.createFrom().item(SdkBytes.fromByteArray(Base64.decodeBase64(data.getBytes())))
+ .onItem().produceCompletionStage(msg -> kms.decrypt(req -> req.keyId(keyArn).ciphertextBlob(msg)))
+ .onItem().apply(DecryptResponse::plaintext)
+ .onItem().apply(SdkBytes::asUtf8String);
+ }
+}
+----
+We create `Uni` instances from the `CompletionStage` objects returned by the asynchronous KMS client, and then transform the emitted item.
+
+And we need to add the Netty HTTP client dependency to the `pom.xml`:
+
+[source,xml]
+----
+
+ software.amazon.awssdk
+ netty-nio-client
+
+----
+
+== Configuration Reference
+
+include::{generated-dir}/config/quarkus-amazon-kms.adoc[opts=optional, leveloffset=+1]
diff --git a/docs/src/main/asciidoc/amazon-ses.adoc b/docs/src/main/asciidoc/amazon-ses.adoc
new file mode 100644
index 0000000000000..32c039b927453
--- /dev/null
+++ b/docs/src/main/asciidoc/amazon-ses.adoc
@@ -0,0 +1,214 @@
+////
+This guide is maintained in the main Quarkus repository
+and pull requests should be submitted there:
+https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc
+////
+= Quarkus - Amazon SES Client
+:extension-status: preview
+
+include::./attributes.adoc[]
+
+Amazon Simple Email Service (SES) is a flexible and highly-scalable email sending and receiving service.
+Using SES, you can send emails with any type of correspondence. You can find more information about SES at https://aws.amazon.com/ses/[the Amazon SES website].
+
+NOTE: The SES extension is based on https://docs.aws.amazon.com/sdk-for-java/v2/developer-guide/welcome.html[AWS Java SDK 2.x].
+It's a major rewrite of the 1.x code base that offers two programming models (Blocking & Async).
+
+include::./status-include.adoc[]
+
+The Quarkus extension supports two programming models:
+
+* Blocking access using URL Connection HTTP client (by default) or the Apache HTTP Client
+* https://docs.aws.amazon.com/sdk-for-java/v2/developer-guide/basics-async.html[Asynchronous programming] based on JDK's `CompletableFuture` objects and the Netty HTTP client.
+
+In this guide, we see how you can get your REST services to use SES locally and on AWS.
+
+== Prerequisites
+
+To complete this guide, you need:
+
+* JDK 1.8+ installed with `JAVA_HOME` configured appropriately
+* an IDE
+* Apache Maven {maven-version}
+* An AWS Account to access the SES service
+* Docker for your system to run SES locally for testing purposes
+
+== Set up SES locally
+
+The easiest way to start working with SES is to run a local instance as a container.
+However, local instance of SES is only mocks the SES APIs without the actual email sending capabilities. You can still use it for this guide to verify an API communication or integration test purposes.
+
+[source,shell,subs="verbatim,attributes"]
+----
+docker run --rm --name local-ses 8012:4579 -e SERVICES=ses -e START_WEB=0 -d localstack/localstack:0.11.1
+----
+This starts a SES instance that is accessible on port `8012`.
+
+Create an AWS profile for your local instance using AWS CLI:
+[source,shell,subs="verbatim,attributes"]
+----
+$ aws configure --profile localstack
+AWS Access Key ID [None]: test-key
+AWS Secret Access Key [None]: test-secret
+Default region name [None]: us-east-1
+Default output format [None]:
+----
+
+== Using SES on your AWS account
+
+Amazon applies certain restrictions to new Amazon SES accounts, mainly to prevent fraud and abuse. All new accounts are in the Amazon SES *sandbox*.
+All the features of the Amazon SES are still available while in sandbox, but a following restrictions applies:
+- You can send mail to verified email addresses and domains or to the https://docs.aws.amazon.com/ses/latest/DeveloperGuide/send-email-simulator.html[Amazon SES mailbox simulator]
+- You can only send mail from verified email addresses and domains
+- You can send a maximum of 1 message per second.
+
+Going production, you'd need to get your account of the sandbox following the https://docs.aws.amazon.com/ses/latest/DeveloperGuide/request-production-access.html[Amazon procedure].
+
+=== Set up AWS SES
+
+We assume you are going to use AWS SES sandbox for the sake of this guide. But before sending any email, you must verify sender and recipient email addresses using AWS CLI.
+You can use your personal email or any temporary email service available if you wish.
+
+[source,shell,subs="verbatim,attributes"]
+----
+aws ses verify-email-identity --email-address
+aws ses verify-email-identity --email-address
+----
+Now, you need to open a mailboxes of those email addresses in order to follow confirmation procedure. Once email is approved you can use it in your application.
+
+If you are using local SES you still need to verify email addresses, otherwise your send emaik in order to let local SES accepting your request.
+However, no emails to be send as it only mocks the service APIs.
+
+[source,shell,subs="verbatim,attributes"]
+----
+aws ses verify-email-identity --email-address --profile localstack --endpoint-url=http://localhost:8012
+aws ses verify-email-identity --email-address --profile localstack --endpoint-url=http://localhost:8012
+----
+
+== Solution
+The application built here allows sending text emails to the recipients that are verified on AWS SES.
+
+We recommend that you follow the instructions in the next sections and create the application step by step.
+However, you can go right to the completed example.
+
+Clone the Git repository: `git clone {quickstarts-clone-url}`, or download an {quickstarts-archive-url}[archive].
+
+The solution is located in the `amazon-ses-quickstart` {quickstarts-tree-url}/amazon-ses-quickstart[directory].
+
+== Creating the Maven project
+
+First, we need a new project. Create a new project with the following command:
+
+[source,shell,subs=attributes+]
+----
+mvn io.quarkus:quarkus-maven-plugin:{quarkus-version}:create \
+ -DprojectGroupId=org.acme \
+ -DprojectArtifactId=amazon-ses-quickstart \
+ -DclassName="org.acme.ses.QuarkusSesSyncResource" \
+ -Dpath="/sync" \
+ -Dextensions="resteasy-jsonb,amazon-ses,resteasy-mutiny"
+cd amazon-ses-quickstart
+----
+
+This command generates a Maven structure importing the RESTEasy/JAX-RS, Mutiny and Amazon SES Client extensions.
+After this, the `amazon-ses` extension has been added to your `pom.xml` as well as the Mutiny support for RESTEasy.
+
+== Creating JSON REST service
+
+Lets create a `org.acme.ses.QuarkusSesSyncResource` that will provide an API to send emails using the synchronous client.
+
+[source,java]
+----
+{ }
+----
+
+== Configuring SES clients
+
+Both SES clients (sync and async) are configurable via the `application.properties` file that can be provided in the `src/main/resources` directory.
+Additionally, you need to add to the classpath a proper implementation of the sync client. By default the extension uses the URL connection HTTP client, so
+you need to add a URL connection client dependency to the `pom.xml` file:
+
+[source,xml]
+----
+
+ software.amazon.awssdk
+ url-connection-client
+
+----
+
+If you want to use Apache HTTP client instead, configure it as follows:
+[source,properties]
+----
+quarkus.ses.sync-client.type=apache
+----
+
+And add the following dependency to the application `pom.xml`:
+[source,xml]
+----
+
+ software.amazon.awssdk
+ apache-client
+
+----
+
+If you're going to use a local SES instance, configure it as follows:
+
+[source,properties]
+----
+quarkus.ses.endpoint-override=http://localhost:8012
+
+quarkus.ses.aws.region=us-east-1
+quarkus.ses.aws.credentials.type=static
+quarkus.ses.aws.credentials.static-provider.access-key-id=test-key
+quarkus.ses.aws.credentials.static-provider.secret-access-key=test-secret
+----
+
+- `quarkus.ses.aws.region` - It's required by the client, but since you're using a local SES instance use `us-east-1` as it's a default region of localstack's SES.
+- `quarkus.ses.aws.credentials.type` - Set `static` credentials provider with any values for `access-key-id` and `secret-access-key`
+- `quarkus.ses.endpoint-override` - Override the SES client to use a local instance instead of an AWS service
+
+If you want to work with an AWS account, you can simply remove or comment out all Amazon SES related properties. By default, the SES client extension
+will use the `default` credentials provider chain that looks for credentials in this order:
+- Java System Properties - `aws.accessKeyId` and `aws.secretKey`
+* Environment Variables - `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`
+* Credential profiles file at the default location (`~/.aws/credentials`) shared by all AWS SDKs and the AWS CLI
+* Credentials delivered through the Amazon EC2 container service if the `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` environment variable is set and the security manager has permission to access the variable,
+* Instance profile credentials delivered through the Amazon EC2 metadata service
+
+And the region from your AWS CLI profile will be used.
+
+== Next steps
+
+=== Packaging
+
+Packaging your application is as simple as `./mvnw clean package`.
+It can be run with `java -jar target/amazon-ses-quickstart-1.0-SNAPSHOT-runner.jar`.
+
+With GraalVM installed, you can also create a native executable binary: `./mvnw clean package -Dnative`.
+Depending on your system, that will take some time.
+
+=== Going asynchronous
+
+Thanks to the AWS SDK v2.x used by the Quarkus extension, you can use the asynchronous programming model out of the box.
+
+Create a `org.acme.ses.QuarkusSesAsyncResource` REST resource that will be similar to our `QuarkusSesSyncResource` but using an asynchronous programming model.
+
+[source,java]
+----
+{ }
+----
+We create `Uni` instances from the `CompletionStage` objects returned by the asynchronous SES client, and then transform the emitted item.
+
+And we need to add the Netty HTTP client dependency to the `pom.xml`:
+
+[source,xml]
+----
+
+ software.amazon.awssdk
+ netty-nio-client
+
+----
+
+== Configuration Reference
+
+include::{generated-dir}/config/quarkus-amazon-ses.adoc[opts=optional, leveloffset=+1]
diff --git a/extensions/amazon-services/kms/deployment/pom.xml b/extensions/amazon-services/kms/deployment/pom.xml
new file mode 100644
index 0000000000000..d5cd3a0fa9c4d
--- /dev/null
+++ b/extensions/amazon-services/kms/deployment/pom.xml
@@ -0,0 +1,77 @@
+
+
+ 4.0.0
+
+
+ io.quarkus
+ quarkus-amazon-kms-parent
+ 999-SNAPSHOT
+
+
+ quarkus-amazon-kms-deployment
+ Quarkus - Amazon Services - KMS - Deployment
+
+
+
+ io.quarkus
+ quarkus-core-deployment
+
+
+ io.quarkus
+ quarkus-arc-deployment
+
+
+ io.quarkus
+ quarkus-netty-deployment
+
+
+ io.quarkus
+ quarkus-amazon-common-deployment
+
+
+ io.quarkus
+ quarkus-amazon-kms
+
+
+
+
+ io.quarkus
+ quarkus-junit5-internal
+ test
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+ software.amazon.awssdk
+ netty-nio-client
+ test
+
+
+ software.amazon.awssdk
+ url-connection-client
+ test
+
+
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${project.version}
+
+
+
+
+
+
+
diff --git a/extensions/amazon-services/kms/deployment/src/main/java/io/quarkus/amazon/kms/deployment/KmsProcessor.java b/extensions/amazon-services/kms/deployment/src/main/java/io/quarkus/amazon/kms/deployment/KmsProcessor.java
new file mode 100644
index 0000000000000..435ebef041ed7
--- /dev/null
+++ b/extensions/amazon-services/kms/deployment/src/main/java/io/quarkus/amazon/kms/deployment/KmsProcessor.java
@@ -0,0 +1,122 @@
+package io.quarkus.amazon.kms.deployment;
+
+import java.util.List;
+
+import org.jboss.jandex.DotName;
+
+import io.quarkus.amazon.common.deployment.AbstractAmazonServiceProcessor;
+import io.quarkus.amazon.common.deployment.AmazonClientBuildItem;
+import io.quarkus.amazon.common.deployment.AmazonClientBuilderBuildItem;
+import io.quarkus.amazon.common.deployment.AmazonClientBuilderConfiguredBuildItem;
+import io.quarkus.amazon.common.deployment.AmazonClientInterceptorsPathBuildItem;
+import io.quarkus.amazon.common.deployment.AmazonClientTransportsBuildItem;
+import io.quarkus.amazon.common.runtime.AmazonClientRecorder;
+import io.quarkus.amazon.common.runtime.AmazonClientTransportRecorder;
+import io.quarkus.amazon.kms.runtime.KmsBuildTimeConfig;
+import io.quarkus.amazon.kms.runtime.KmsClientProducer;
+import io.quarkus.amazon.kms.runtime.KmsConfig;
+import io.quarkus.amazon.kms.runtime.KmsRecorder;
+import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
+import io.quarkus.arc.deployment.BeanContainerBuildItem;
+import io.quarkus.arc.deployment.BeanRegistrationPhaseBuildItem;
+import io.quarkus.deployment.annotations.BuildProducer;
+import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.annotations.ExecutionTime;
+import io.quarkus.deployment.annotations.Record;
+import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem;
+import io.quarkus.deployment.builditem.FeatureBuildItem;
+import io.quarkus.deployment.builditem.ShutdownContextBuildItem;
+import software.amazon.awssdk.services.kms.KmsAsyncClient;
+import software.amazon.awssdk.services.kms.KmsClient;
+
+public class KmsProcessor extends AbstractAmazonServiceProcessor {
+
+ KmsBuildTimeConfig buildTimeConfig;
+
+ @Override
+ protected String amazonServiceClientName() {
+ return FeatureBuildItem.AMAZON_KMS;
+ }
+
+ @Override
+ protected String configName() {
+ return "kms";
+ }
+
+ @Override
+ protected DotName syncClientName() {
+ return DotName.createSimple(KmsClient.class.getName());
+ }
+
+ @Override
+ protected DotName asyncClientName() {
+ return DotName.createSimple(KmsAsyncClient.class.getName());
+ }
+
+ @Override
+ protected String builtinInterceptorsPath() {
+ return "software/amazon/awssdk/services/kms/execution.interceptors";
+ }
+
+ @BuildStep
+ AdditionalBeanBuildItem producer() {
+ return AdditionalBeanBuildItem.unremovableOf(KmsClientProducer.class);
+ }
+
+ @BuildStep
+ void setup(BeanRegistrationPhaseBuildItem beanRegistrationPhase,
+ BuildProducer extensionSslNativeSupport,
+ BuildProducer feature,
+ BuildProducer interceptors,
+ BuildProducer clientProducer) {
+
+ setupExtension(beanRegistrationPhase, extensionSslNativeSupport, feature, interceptors, clientProducer,
+ buildTimeConfig.sdk, buildTimeConfig.syncClient);
+ }
+
+ @BuildStep
+ @Record(ExecutionTime.RUNTIME_INIT)
+ void setupTransport(List amazonClients, KmsRecorder recorder,
+ AmazonClientTransportRecorder transportRecorder,
+ KmsConfig runtimeConfig, BuildProducer clientTransportBuildProducer) {
+
+ createTransportBuilders(amazonClients,
+ transportRecorder,
+ buildTimeConfig.syncClient,
+ recorder.getSyncConfig(runtimeConfig),
+ recorder.getAsyncConfig(runtimeConfig),
+ clientTransportBuildProducer);
+ }
+
+ @BuildStep
+ @Record(ExecutionTime.RUNTIME_INIT)
+ void createClientBuilders(List transportBuildItems, KmsRecorder recorder,
+ KmsConfig runtimeConfig, BuildProducer builderProducer) {
+
+ createClientBuilders(transportBuildItems, builderProducer,
+ (syncTransport) -> recorder.createSyncBuilder(runtimeConfig, syncTransport),
+ (asyncTransport) -> recorder.createAsyncBuilder(runtimeConfig, asyncTransport));
+ }
+
+ @BuildStep
+ @Record(ExecutionTime.RUNTIME_INIT)
+ void configureClient(List clients, KmsRecorder recorder,
+ AmazonClientRecorder commonRecorder,
+ KmsConfig runtimeConfig,
+ BuildProducer producer) {
+
+ initClientBuilders(clients, commonRecorder, recorder.getAwsConfig(runtimeConfig), recorder.getSdkConfig(runtimeConfig),
+ buildTimeConfig.sdk, producer);
+ }
+
+ @BuildStep
+ @Record(ExecutionTime.RUNTIME_INIT)
+ void buildClients(List configuredClients, KmsRecorder recorder,
+ BeanContainerBuildItem beanContainer,
+ ShutdownContextBuildItem shutdown) {
+
+ buildClients(configuredClients,
+ (syncBuilder) -> recorder.buildClient(syncBuilder, beanContainer.getValue(), shutdown),
+ (asyncBuilder) -> recorder.buildAsyncClient(asyncBuilder, beanContainer.getValue(), shutdown));
+ }
+}
diff --git a/extensions/amazon-services/kms/deployment/src/test/java/io/quarkus/amazon/kms/deployment/KmsSyncClientFullConfigTest.java b/extensions/amazon-services/kms/deployment/src/test/java/io/quarkus/amazon/kms/deployment/KmsSyncClientFullConfigTest.java
new file mode 100644
index 0000000000000..daf8ae667b0a1
--- /dev/null
+++ b/extensions/amazon-services/kms/deployment/src/test/java/io/quarkus/amazon/kms/deployment/KmsSyncClientFullConfigTest.java
@@ -0,0 +1,31 @@
+package io.quarkus.amazon.kms.deployment;
+
+import javax.inject.Inject;
+
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusUnitTest;
+import software.amazon.awssdk.services.kms.KmsAsyncClient;
+import software.amazon.awssdk.services.kms.KmsClient;
+
+public class KmsSyncClientFullConfigTest {
+
+ @Inject
+ KmsClient client;
+
+ @Inject
+ KmsAsyncClient async;
+
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest()
+ .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
+ .addAsResource("sync-urlconn-full-config.properties", "application.properties"));
+
+ @Test
+ public void test() {
+ // should finish with success
+ }
+}
diff --git a/extensions/amazon-services/kms/deployment/src/test/resources/sync-urlconn-full-config.properties b/extensions/amazon-services/kms/deployment/src/test/resources/sync-urlconn-full-config.properties
new file mode 100644
index 0000000000000..47f55471e226f
--- /dev/null
+++ b/extensions/amazon-services/kms/deployment/src/test/resources/sync-urlconn-full-config.properties
@@ -0,0 +1,10 @@
+quarkus.kms.endpoint-override=http://localhost:9090
+
+quarkus.kms.aws.region=us-east-1
+quarkus.kms.aws.credentials.type=static
+quarkus.kms.aws.credentials.static-provider.access-key-id=test-key
+quarkus.kms.aws.credentials.static-provider.secret-access-key=test-secret
+
+quarkus.kms.sync-client.type = url
+quarkus.kms.sync-client.connection-timeout = 0.100S
+quarkus.kms.sync-client.socket-timeout = 0.100S
\ No newline at end of file
diff --git a/extensions/amazon-services/kms/pom.xml b/extensions/amazon-services/kms/pom.xml
new file mode 100644
index 0000000000000..180529ce27a35
--- /dev/null
+++ b/extensions/amazon-services/kms/pom.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+
+
+ io.quarkus
+ quarkus-amazon-services-parent
+ 999-SNAPSHOT
+
+
+ quarkus-amazon-kms-parent
+ Quarkus - Amazon Services - KMS
+ pom
+
+
+ runtime
+ deployment
+
+
+
diff --git a/extensions/amazon-services/kms/runtime/pom.xml b/extensions/amazon-services/kms/runtime/pom.xml
new file mode 100644
index 0000000000000..3b22a4dc99619
--- /dev/null
+++ b/extensions/amazon-services/kms/runtime/pom.xml
@@ -0,0 +1,81 @@
+
+
+ 4.0.0
+
+
+ io.quarkus
+ quarkus-amazon-kms-parent
+ 999-SNAPSHOT
+
+
+ quarkus-amazon-kms
+ Quarkus - Amazon Services - KMS - Runtime
+ Connect to Amazon KMS
+
+
+
+ io.quarkus
+ quarkus-core
+
+
+ io.quarkus
+ quarkus-arc
+
+
+ io.quarkus
+ quarkus-netty
+
+
+
+ io.quarkus
+ quarkus-amazon-common
+
+
+ software.amazon.awssdk
+ kms
+
+
+ software.amazon.awssdk
+ netty-nio-client
+ true
+
+
+ software.amazon.awssdk
+ url-connection-client
+ true
+
+
+ software.amazon.awssdk
+ apache-client
+ true
+
+
+
+ org.jboss.logging
+ commons-logging-jboss-logging
+
+
+
+
+
+
+ io.quarkus
+ quarkus-bootstrap-maven-plugin
+
+
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${project.version}
+
+
+
+
+
+
+
diff --git a/extensions/amazon-services/kms/runtime/src/main/java/io/quarkus/amazon/kms/runtime/KmsBuildTimeConfig.java b/extensions/amazon-services/kms/runtime/src/main/java/io/quarkus/amazon/kms/runtime/KmsBuildTimeConfig.java
new file mode 100644
index 0000000000000..d592c0ad62366
--- /dev/null
+++ b/extensions/amazon-services/kms/runtime/src/main/java/io/quarkus/amazon/kms/runtime/KmsBuildTimeConfig.java
@@ -0,0 +1,26 @@
+package io.quarkus.amazon.kms.runtime;
+
+import io.quarkus.amazon.common.runtime.SdkBuildTimeConfig;
+import io.quarkus.amazon.common.runtime.SyncHttpClientBuildTimeConfig;
+import io.quarkus.runtime.annotations.ConfigItem;
+import io.quarkus.runtime.annotations.ConfigPhase;
+import io.quarkus.runtime.annotations.ConfigRoot;
+
+/**
+ * Amazon KMS build time configuration
+ */
+@ConfigRoot(name = "kms", phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED)
+public class KmsBuildTimeConfig {
+
+ /**
+ * SDK client configurations for AWS KMS client
+ */
+ @ConfigItem(name = ConfigItem.PARENT)
+ public SdkBuildTimeConfig sdk;
+
+ /**
+ * Sync HTTP transport configuration for Amazon KMS client
+ */
+ @ConfigItem
+ public SyncHttpClientBuildTimeConfig syncClient;
+}
diff --git a/extensions/amazon-services/kms/runtime/src/main/java/io/quarkus/amazon/kms/runtime/KmsClientProducer.java b/extensions/amazon-services/kms/runtime/src/main/java/io/quarkus/amazon/kms/runtime/KmsClientProducer.java
new file mode 100644
index 0000000000000..33cbc6cf1686e
--- /dev/null
+++ b/extensions/amazon-services/kms/runtime/src/main/java/io/quarkus/amazon/kms/runtime/KmsClientProducer.java
@@ -0,0 +1,52 @@
+package io.quarkus.amazon.kms.runtime;
+
+import javax.annotation.PreDestroy;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.Produces;
+
+import software.amazon.awssdk.services.kms.KmsAsyncClient;
+import software.amazon.awssdk.services.kms.KmsAsyncClientBuilder;
+import software.amazon.awssdk.services.kms.KmsClient;
+import software.amazon.awssdk.services.kms.KmsClientBuilder;
+
+@ApplicationScoped
+public class KmsClientProducer {
+
+ private volatile KmsClientBuilder syncConfiguredBuilder;
+ private volatile KmsAsyncClientBuilder asyncConfiguredBuilder;
+
+ private KmsClient client;
+ private KmsAsyncClient asyncClient;
+
+ @Produces
+ @ApplicationScoped
+ public KmsClient client() {
+ client = syncConfiguredBuilder.build();
+ return client;
+ }
+
+ @Produces
+ @ApplicationScoped
+ public KmsAsyncClient asyncClient() {
+ asyncClient = asyncConfiguredBuilder.build();
+ return asyncClient;
+ }
+
+ @PreDestroy
+ public void destroy() {
+ if (client != null) {
+ client.close();
+ }
+ if (asyncClient != null) {
+ asyncClient.close();
+ }
+ }
+
+ public void setSyncConfiguredBuilder(KmsClientBuilder syncConfiguredBuilder) {
+ this.syncConfiguredBuilder = syncConfiguredBuilder;
+ }
+
+ public void setAsyncConfiguredBuilder(KmsAsyncClientBuilder asyncConfiguredBuilder) {
+ this.asyncConfiguredBuilder = asyncConfiguredBuilder;
+ }
+}
diff --git a/extensions/amazon-services/kms/runtime/src/main/java/io/quarkus/amazon/kms/runtime/KmsConfig.java b/extensions/amazon-services/kms/runtime/src/main/java/io/quarkus/amazon/kms/runtime/KmsConfig.java
new file mode 100644
index 0000000000000..2b3966153a70f
--- /dev/null
+++ b/extensions/amazon-services/kms/runtime/src/main/java/io/quarkus/amazon/kms/runtime/KmsConfig.java
@@ -0,0 +1,42 @@
+package io.quarkus.amazon.kms.runtime;
+
+import io.quarkus.amazon.common.runtime.AwsConfig;
+import io.quarkus.amazon.common.runtime.NettyHttpClientConfig;
+import io.quarkus.amazon.common.runtime.SdkConfig;
+import io.quarkus.amazon.common.runtime.SyncHttpClientConfig;
+import io.quarkus.runtime.annotations.ConfigDocSection;
+import io.quarkus.runtime.annotations.ConfigItem;
+import io.quarkus.runtime.annotations.ConfigPhase;
+import io.quarkus.runtime.annotations.ConfigRoot;
+
+@ConfigRoot(name = "kms", phase = ConfigPhase.RUN_TIME)
+public class KmsConfig {
+
+ /**
+ * AWS SDK client configurations
+ */
+ @ConfigItem(name = ConfigItem.PARENT)
+ @ConfigDocSection
+ public SdkConfig sdk;
+
+ /**
+ * AWS services configurations
+ */
+ @ConfigItem
+ @ConfigDocSection
+ public AwsConfig aws;
+
+ /**
+ * Sync HTTP transport configurations
+ */
+ @ConfigItem
+ @ConfigDocSection
+ public SyncHttpClientConfig syncClient;
+
+ /**
+ * Netty HTTP transport configurations
+ */
+ @ConfigItem
+ @ConfigDocSection
+ public NettyHttpClientConfig asyncClient;
+}
diff --git a/extensions/amazon-services/kms/runtime/src/main/java/io/quarkus/amazon/kms/runtime/KmsRecorder.java b/extensions/amazon-services/kms/runtime/src/main/java/io/quarkus/amazon/kms/runtime/KmsRecorder.java
new file mode 100644
index 0000000000000..8cfb835ccfac7
--- /dev/null
+++ b/extensions/amazon-services/kms/runtime/src/main/java/io/quarkus/amazon/kms/runtime/KmsRecorder.java
@@ -0,0 +1,73 @@
+package io.quarkus.amazon.kms.runtime;
+
+import io.quarkus.amazon.common.runtime.AwsConfig;
+import io.quarkus.amazon.common.runtime.NettyHttpClientConfig;
+import io.quarkus.amazon.common.runtime.SdkConfig;
+import io.quarkus.amazon.common.runtime.SyncHttpClientConfig;
+import io.quarkus.arc.runtime.BeanContainer;
+import io.quarkus.runtime.RuntimeValue;
+import io.quarkus.runtime.ShutdownContext;
+import io.quarkus.runtime.annotations.Recorder;
+import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder;
+import software.amazon.awssdk.http.SdkHttpClient;
+import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
+import software.amazon.awssdk.services.kms.KmsAsyncClient;
+import software.amazon.awssdk.services.kms.KmsAsyncClientBuilder;
+import software.amazon.awssdk.services.kms.KmsClient;
+import software.amazon.awssdk.services.kms.KmsClientBuilder;
+
+@Recorder
+public class KmsRecorder {
+
+ public RuntimeValue getSyncConfig(KmsConfig config) {
+ return new RuntimeValue<>(config.syncClient);
+ }
+
+ public RuntimeValue getAsyncConfig(KmsConfig config) {
+ return new RuntimeValue<>(config.asyncClient);
+ }
+
+ public RuntimeValue getAwsConfig(KmsConfig config) {
+ return new RuntimeValue<>(config.aws);
+ }
+
+ public RuntimeValue getSdkConfig(KmsConfig config) {
+ return new RuntimeValue<>(config.sdk);
+ }
+
+ public RuntimeValue createSyncBuilder(KmsConfig config, RuntimeValue transport) {
+ KmsClientBuilder builder = KmsClient.builder();
+ if (transport != null) {
+ builder.httpClientBuilder(transport.getValue());
+ }
+ return new RuntimeValue<>(builder);
+ }
+
+ public RuntimeValue createAsyncBuilder(KmsConfig config,
+ RuntimeValue transport) {
+
+ KmsAsyncClientBuilder builder = KmsAsyncClient.builder();
+ if (transport != null) {
+ builder.httpClientBuilder(transport.getValue());
+ }
+ return new RuntimeValue<>(builder);
+ }
+
+ public RuntimeValue buildClient(RuntimeValue extends AwsClientBuilder> builder,
+ BeanContainer beanContainer,
+ ShutdownContext shutdown) {
+ KmsClientProducer producer = beanContainer.instance(KmsClientProducer.class);
+ producer.setSyncConfiguredBuilder((KmsClientBuilder) builder.getValue());
+ shutdown.addShutdownTask(producer::destroy);
+ return new RuntimeValue<>(producer.client());
+ }
+
+ public RuntimeValue buildAsyncClient(RuntimeValue extends AwsClientBuilder> builder,
+ BeanContainer beanContainer,
+ ShutdownContext shutdown) {
+ KmsClientProducer producer = beanContainer.instance(KmsClientProducer.class);
+ producer.setAsyncConfiguredBuilder((KmsAsyncClientBuilder) builder.getValue());
+ shutdown.addShutdownTask(producer::destroy);
+ return new RuntimeValue<>(producer.asyncClient());
+ }
+}
diff --git a/extensions/amazon-services/kms/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/amazon-services/kms/runtime/src/main/resources/META-INF/quarkus-extension.yaml
new file mode 100644
index 0000000000000..3a0f026ca12dd
--- /dev/null
+++ b/extensions/amazon-services/kms/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -0,0 +1,11 @@
+---
+name: "Amazon KMS"
+metadata:
+ keywords:
+ - "kms"
+ - "aws"
+ - "amazon"
+ guide: "https://quarkus.io/guides/amazon-kms"
+ categories:
+ - "data"
+ status: "preview"
diff --git a/extensions/amazon-services/pom.xml b/extensions/amazon-services/pom.xml
index a05c8454e3ea0..e973bc3730751 100644
--- a/extensions/amazon-services/pom.xml
+++ b/extensions/amazon-services/pom.xml
@@ -20,5 +20,7 @@
s3
sns
sqs
+ ses
+ kms
diff --git a/extensions/amazon-services/ses/deployment/pom.xml b/extensions/amazon-services/ses/deployment/pom.xml
new file mode 100644
index 0000000000000..2b4e31d71e4b6
--- /dev/null
+++ b/extensions/amazon-services/ses/deployment/pom.xml
@@ -0,0 +1,77 @@
+
+
+ 4.0.0
+
+
+ io.quarkus
+ quarkus-amazon-ses-parent
+ 999-SNAPSHOT
+
+
+ quarkus-amazon-ses-deployment
+ Quarkus - Amazon Services - SES - Deployment
+
+
+
+ io.quarkus
+ quarkus-core-deployment
+
+
+ io.quarkus
+ quarkus-arc-deployment
+
+
+ io.quarkus
+ quarkus-netty-deployment
+
+
+ io.quarkus
+ quarkus-amazon-common-deployment
+
+
+ io.quarkus
+ quarkus-amazon-ses
+
+
+
+
+ io.quarkus
+ quarkus-junit5-internal
+ test
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+ software.amazon.awssdk
+ netty-nio-client
+ test
+
+
+ software.amazon.awssdk
+ url-connection-client
+ test
+
+
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${project.version}
+
+
+
+
+
+
+
diff --git a/extensions/amazon-services/ses/deployment/src/main/java/io/quarkus/amazon/ses/deployment/SesProcessor.java b/extensions/amazon-services/ses/deployment/src/main/java/io/quarkus/amazon/ses/deployment/SesProcessor.java
new file mode 100644
index 0000000000000..28acdef800e8e
--- /dev/null
+++ b/extensions/amazon-services/ses/deployment/src/main/java/io/quarkus/amazon/ses/deployment/SesProcessor.java
@@ -0,0 +1,122 @@
+package io.quarkus.amazon.ses.deployment;
+
+import java.util.List;
+
+import org.jboss.jandex.DotName;
+
+import io.quarkus.amazon.common.deployment.AbstractAmazonServiceProcessor;
+import io.quarkus.amazon.common.deployment.AmazonClientBuildItem;
+import io.quarkus.amazon.common.deployment.AmazonClientBuilderBuildItem;
+import io.quarkus.amazon.common.deployment.AmazonClientBuilderConfiguredBuildItem;
+import io.quarkus.amazon.common.deployment.AmazonClientInterceptorsPathBuildItem;
+import io.quarkus.amazon.common.deployment.AmazonClientTransportsBuildItem;
+import io.quarkus.amazon.common.runtime.AmazonClientRecorder;
+import io.quarkus.amazon.common.runtime.AmazonClientTransportRecorder;
+import io.quarkus.amazon.ses.runtime.SesBuildTimeConfig;
+import io.quarkus.amazon.ses.runtime.SesClientProducer;
+import io.quarkus.amazon.ses.runtime.SesConfig;
+import io.quarkus.amazon.ses.runtime.SesRecorder;
+import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
+import io.quarkus.arc.deployment.BeanContainerBuildItem;
+import io.quarkus.arc.deployment.BeanRegistrationPhaseBuildItem;
+import io.quarkus.deployment.annotations.BuildProducer;
+import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.annotations.ExecutionTime;
+import io.quarkus.deployment.annotations.Record;
+import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem;
+import io.quarkus.deployment.builditem.FeatureBuildItem;
+import io.quarkus.deployment.builditem.ShutdownContextBuildItem;
+import software.amazon.awssdk.services.ses.SesAsyncClient;
+import software.amazon.awssdk.services.ses.SesClient;
+
+public class SesProcessor extends AbstractAmazonServiceProcessor {
+
+ SesBuildTimeConfig buildTimeConfig;
+
+ @Override
+ protected String amazonServiceClientName() {
+ return FeatureBuildItem.AMAZON_SES;
+ }
+
+ @Override
+ protected String configName() {
+ return "ses";
+ }
+
+ @Override
+ protected DotName syncClientName() {
+ return DotName.createSimple(SesClient.class.getName());
+ }
+
+ @Override
+ protected DotName asyncClientName() {
+ return DotName.createSimple(SesAsyncClient.class.getName());
+ }
+
+ @Override
+ protected String builtinInterceptorsPath() {
+ return "software/amazon/awssdk/services/ses/execution.interceptors";
+ }
+
+ @BuildStep
+ AdditionalBeanBuildItem producer() {
+ return AdditionalBeanBuildItem.unremovableOf(SesClientProducer.class);
+ }
+
+ @BuildStep
+ void setup(BeanRegistrationPhaseBuildItem beanRegistrationPhase,
+ BuildProducer extensionSslNativeSupport,
+ BuildProducer feature,
+ BuildProducer interceptors,
+ BuildProducer clientProducer) {
+
+ setupExtension(beanRegistrationPhase, extensionSslNativeSupport, feature, interceptors, clientProducer,
+ buildTimeConfig.sdk, buildTimeConfig.syncClient);
+ }
+
+ @BuildStep
+ @Record(ExecutionTime.RUNTIME_INIT)
+ void setupTransport(List amazonClients, SesRecorder recorder,
+ AmazonClientTransportRecorder transportRecorder,
+ SesConfig runtimeConfig, BuildProducer clientTransportBuildProducer) {
+
+ createTransportBuilders(amazonClients,
+ transportRecorder,
+ buildTimeConfig.syncClient,
+ recorder.getSyncConfig(runtimeConfig),
+ recorder.getAsyncConfig(runtimeConfig),
+ clientTransportBuildProducer);
+ }
+
+ @BuildStep
+ @Record(ExecutionTime.RUNTIME_INIT)
+ void createClientBuilders(List transportBuildItems, SesRecorder recorder,
+ SesConfig runtimeConfig, BuildProducer builderProducer) {
+
+ createClientBuilders(transportBuildItems, builderProducer,
+ (syncTransport) -> recorder.createSyncBuilder(runtimeConfig, syncTransport),
+ (asyncTransport) -> recorder.createAsyncBuilder(runtimeConfig, asyncTransport));
+ }
+
+ @BuildStep
+ @Record(ExecutionTime.RUNTIME_INIT)
+ void configureClient(List clients, SesRecorder recorder,
+ AmazonClientRecorder commonRecorder,
+ SesConfig runtimeConfig,
+ BuildProducer producer) {
+
+ initClientBuilders(clients, commonRecorder, recorder.getAwsConfig(runtimeConfig), recorder.getSdkConfig(runtimeConfig),
+ buildTimeConfig.sdk, producer);
+ }
+
+ @BuildStep
+ @Record(ExecutionTime.RUNTIME_INIT)
+ void buildClients(List configuredClients, SesRecorder recorder,
+ BeanContainerBuildItem beanContainer,
+ ShutdownContextBuildItem shutdown) {
+
+ buildClients(configuredClients,
+ (syncBuilder) -> recorder.buildClient(syncBuilder, beanContainer.getValue(), shutdown),
+ (asyncBuilder) -> recorder.buildAsyncClient(asyncBuilder, beanContainer.getValue(), shutdown));
+ }
+}
diff --git a/extensions/amazon-services/ses/deployment/src/test/java/io/quarkus/amazon/ses/deployment/SesSyncClientFullConfigTest.java b/extensions/amazon-services/ses/deployment/src/test/java/io/quarkus/amazon/ses/deployment/SesSyncClientFullConfigTest.java
new file mode 100644
index 0000000000000..9ddc5e2c89425
--- /dev/null
+++ b/extensions/amazon-services/ses/deployment/src/test/java/io/quarkus/amazon/ses/deployment/SesSyncClientFullConfigTest.java
@@ -0,0 +1,31 @@
+package io.quarkus.amazon.ses.deployment;
+
+import javax.inject.Inject;
+
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusUnitTest;
+import software.amazon.awssdk.services.ses.SesAsyncClient;
+import software.amazon.awssdk.services.ses.SesClient;
+
+public class SesSyncClientFullConfigTest {
+
+ @Inject
+ SesClient client;
+
+ @Inject
+ SesAsyncClient async;
+
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest()
+ .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
+ .addAsResource("sync-urlconn-full-config.properties", "application.properties"));
+
+ @Test
+ public void test() {
+ // should finish with success
+ }
+}
diff --git a/extensions/amazon-services/ses/deployment/src/test/resources/sync-urlconn-full-config.properties b/extensions/amazon-services/ses/deployment/src/test/resources/sync-urlconn-full-config.properties
new file mode 100644
index 0000000000000..bd31e7b1efe3d
--- /dev/null
+++ b/extensions/amazon-services/ses/deployment/src/test/resources/sync-urlconn-full-config.properties
@@ -0,0 +1,10 @@
+quarkus.ses.endpoint-override=http://localhost:9090
+
+quarkus.ses.aws.region=us-east-1
+quarkus.ses.aws.credentials.type=static
+quarkus.ses.aws.credentials.static-provider.access-key-id=test-key
+quarkus.ses.aws.credentials.static-provider.secret-access-key=test-secret
+
+quarkus.ses.sync-client.type = url
+quarkus.ses.sync-client.connection-timeout = 0.100S
+quarkus.ses.sync-client.socket-timeout = 0.100S
\ No newline at end of file
diff --git a/extensions/amazon-services/ses/pom.xml b/extensions/amazon-services/ses/pom.xml
new file mode 100644
index 0000000000000..3336a82cc88d5
--- /dev/null
+++ b/extensions/amazon-services/ses/pom.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+
+
+ io.quarkus
+ quarkus-amazon-services-parent
+ 999-SNAPSHOT
+
+
+ quarkus-amazon-ses-parent
+ Quarkus - Amazon Services - SES
+ pom
+
+
+ runtime
+ deployment
+
+
+
diff --git a/extensions/amazon-services/ses/runtime/pom.xml b/extensions/amazon-services/ses/runtime/pom.xml
new file mode 100644
index 0000000000000..f7f80fee7191b
--- /dev/null
+++ b/extensions/amazon-services/ses/runtime/pom.xml
@@ -0,0 +1,81 @@
+
+
+ 4.0.0
+
+
+ io.quarkus
+ quarkus-amazon-ses-parent
+ 999-SNAPSHOT
+
+
+ quarkus-amazon-ses
+ Quarkus - Amazon Services - SES - Runtime
+ Connect to Amazon SES
+
+
+
+ io.quarkus
+ quarkus-core
+
+
+ io.quarkus
+ quarkus-arc
+
+
+ io.quarkus
+ quarkus-netty
+
+
+
+ io.quarkus
+ quarkus-amazon-common
+
+
+ software.amazon.awssdk
+ ses
+
+
+ software.amazon.awssdk
+ netty-nio-client
+ true
+
+
+ software.amazon.awssdk
+ url-connection-client
+ true
+
+
+ software.amazon.awssdk
+ apache-client
+ true
+
+
+
+ org.jboss.logging
+ commons-logging-jboss-logging
+
+
+
+
+
+
+ io.quarkus
+ quarkus-bootstrap-maven-plugin
+
+
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${project.version}
+
+
+
+
+
+
+
diff --git a/extensions/amazon-services/ses/runtime/src/main/java/io/quarkus/amazon/ses/runtime/SesBuildTimeConfig.java b/extensions/amazon-services/ses/runtime/src/main/java/io/quarkus/amazon/ses/runtime/SesBuildTimeConfig.java
new file mode 100644
index 0000000000000..a49fa703cde77
--- /dev/null
+++ b/extensions/amazon-services/ses/runtime/src/main/java/io/quarkus/amazon/ses/runtime/SesBuildTimeConfig.java
@@ -0,0 +1,26 @@
+package io.quarkus.amazon.ses.runtime;
+
+import io.quarkus.amazon.common.runtime.SdkBuildTimeConfig;
+import io.quarkus.amazon.common.runtime.SyncHttpClientBuildTimeConfig;
+import io.quarkus.runtime.annotations.ConfigItem;
+import io.quarkus.runtime.annotations.ConfigPhase;
+import io.quarkus.runtime.annotations.ConfigRoot;
+
+/**
+ * Amazon SES build time configuration
+ */
+@ConfigRoot(name = "ses", phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED)
+public class SesBuildTimeConfig {
+
+ /**
+ * SDK client configurations for AWS SES client
+ */
+ @ConfigItem(name = ConfigItem.PARENT)
+ public SdkBuildTimeConfig sdk;
+
+ /**
+ * Sync HTTP transport configuration for Amazon SES client
+ */
+ @ConfigItem
+ public SyncHttpClientBuildTimeConfig syncClient;
+}
diff --git a/extensions/amazon-services/ses/runtime/src/main/java/io/quarkus/amazon/ses/runtime/SesClientProducer.java b/extensions/amazon-services/ses/runtime/src/main/java/io/quarkus/amazon/ses/runtime/SesClientProducer.java
new file mode 100644
index 0000000000000..fbc95a83a91c3
--- /dev/null
+++ b/extensions/amazon-services/ses/runtime/src/main/java/io/quarkus/amazon/ses/runtime/SesClientProducer.java
@@ -0,0 +1,52 @@
+package io.quarkus.amazon.ses.runtime;
+
+import javax.annotation.PreDestroy;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.Produces;
+
+import software.amazon.awssdk.services.ses.SesAsyncClient;
+import software.amazon.awssdk.services.ses.SesAsyncClientBuilder;
+import software.amazon.awssdk.services.ses.SesClient;
+import software.amazon.awssdk.services.ses.SesClientBuilder;
+
+@ApplicationScoped
+public class SesClientProducer {
+
+ private volatile SesClientBuilder syncConfiguredBuilder;
+ private volatile SesAsyncClientBuilder asyncConfiguredBuilder;
+
+ private SesClient client;
+ private SesAsyncClient asyncClient;
+
+ @Produces
+ @ApplicationScoped
+ public SesClient client() {
+ client = syncConfiguredBuilder.build();
+ return client;
+ }
+
+ @Produces
+ @ApplicationScoped
+ public SesAsyncClient asyncClient() {
+ asyncClient = asyncConfiguredBuilder.build();
+ return asyncClient;
+ }
+
+ @PreDestroy
+ public void destroy() {
+ if (client != null) {
+ client.close();
+ }
+ if (asyncClient != null) {
+ asyncClient.close();
+ }
+ }
+
+ public void setSyncConfiguredBuilder(SesClientBuilder syncConfiguredBuilder) {
+ this.syncConfiguredBuilder = syncConfiguredBuilder;
+ }
+
+ public void setAsyncConfiguredBuilder(SesAsyncClientBuilder asyncConfiguredBuilder) {
+ this.asyncConfiguredBuilder = asyncConfiguredBuilder;
+ }
+}
diff --git a/extensions/amazon-services/ses/runtime/src/main/java/io/quarkus/amazon/ses/runtime/SesConfig.java b/extensions/amazon-services/ses/runtime/src/main/java/io/quarkus/amazon/ses/runtime/SesConfig.java
new file mode 100644
index 0000000000000..635baed3d5058
--- /dev/null
+++ b/extensions/amazon-services/ses/runtime/src/main/java/io/quarkus/amazon/ses/runtime/SesConfig.java
@@ -0,0 +1,42 @@
+package io.quarkus.amazon.ses.runtime;
+
+import io.quarkus.amazon.common.runtime.AwsConfig;
+import io.quarkus.amazon.common.runtime.NettyHttpClientConfig;
+import io.quarkus.amazon.common.runtime.SdkConfig;
+import io.quarkus.amazon.common.runtime.SyncHttpClientConfig;
+import io.quarkus.runtime.annotations.ConfigDocSection;
+import io.quarkus.runtime.annotations.ConfigItem;
+import io.quarkus.runtime.annotations.ConfigPhase;
+import io.quarkus.runtime.annotations.ConfigRoot;
+
+@ConfigRoot(name = "ses", phase = ConfigPhase.RUN_TIME)
+public class SesConfig {
+
+ /**
+ * AWS SDK client configurations
+ */
+ @ConfigItem(name = ConfigItem.PARENT)
+ @ConfigDocSection
+ public SdkConfig sdk;
+
+ /**
+ * AWS services configurations
+ */
+ @ConfigItem
+ @ConfigDocSection
+ public AwsConfig aws;
+
+ /**
+ * Sync HTTP transport configurations
+ */
+ @ConfigItem
+ @ConfigDocSection
+ public SyncHttpClientConfig syncClient;
+
+ /**
+ * Netty HTTP transport configurations
+ */
+ @ConfigItem
+ @ConfigDocSection
+ public NettyHttpClientConfig asyncClient;
+}
diff --git a/extensions/amazon-services/ses/runtime/src/main/java/io/quarkus/amazon/ses/runtime/SesRecorder.java b/extensions/amazon-services/ses/runtime/src/main/java/io/quarkus/amazon/ses/runtime/SesRecorder.java
new file mode 100644
index 0000000000000..51dae0c8e85ab
--- /dev/null
+++ b/extensions/amazon-services/ses/runtime/src/main/java/io/quarkus/amazon/ses/runtime/SesRecorder.java
@@ -0,0 +1,73 @@
+package io.quarkus.amazon.ses.runtime;
+
+import io.quarkus.amazon.common.runtime.AwsConfig;
+import io.quarkus.amazon.common.runtime.NettyHttpClientConfig;
+import io.quarkus.amazon.common.runtime.SdkConfig;
+import io.quarkus.amazon.common.runtime.SyncHttpClientConfig;
+import io.quarkus.arc.runtime.BeanContainer;
+import io.quarkus.runtime.RuntimeValue;
+import io.quarkus.runtime.ShutdownContext;
+import io.quarkus.runtime.annotations.Recorder;
+import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder;
+import software.amazon.awssdk.http.SdkHttpClient;
+import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
+import software.amazon.awssdk.services.ses.SesAsyncClient;
+import software.amazon.awssdk.services.ses.SesAsyncClientBuilder;
+import software.amazon.awssdk.services.ses.SesClient;
+import software.amazon.awssdk.services.ses.SesClientBuilder;
+
+@Recorder
+public class SesRecorder {
+
+ public RuntimeValue getSyncConfig(SesConfig config) {
+ return new RuntimeValue<>(config.syncClient);
+ }
+
+ public RuntimeValue getAsyncConfig(SesConfig config) {
+ return new RuntimeValue<>(config.asyncClient);
+ }
+
+ public RuntimeValue getAwsConfig(SesConfig config) {
+ return new RuntimeValue<>(config.aws);
+ }
+
+ public RuntimeValue getSdkConfig(SesConfig config) {
+ return new RuntimeValue<>(config.sdk);
+ }
+
+ public RuntimeValue createSyncBuilder(SesConfig config, RuntimeValue transport) {
+ SesClientBuilder builder = SesClient.builder();
+ if (transport != null) {
+ builder.httpClientBuilder(transport.getValue());
+ }
+ return new RuntimeValue<>(builder);
+ }
+
+ public RuntimeValue createAsyncBuilder(SesConfig config,
+ RuntimeValue transport) {
+
+ SesAsyncClientBuilder builder = SesAsyncClient.builder();
+ if (transport != null) {
+ builder.httpClientBuilder(transport.getValue());
+ }
+ return new RuntimeValue<>(builder);
+ }
+
+ public RuntimeValue buildClient(RuntimeValue extends AwsClientBuilder> builder,
+ BeanContainer beanContainer,
+ ShutdownContext shutdown) {
+ SesClientProducer producer = beanContainer.instance(SesClientProducer.class);
+ producer.setSyncConfiguredBuilder((SesClientBuilder) builder.getValue());
+ shutdown.addShutdownTask(producer::destroy);
+ return new RuntimeValue<>(producer.client());
+ }
+
+ public RuntimeValue buildAsyncClient(RuntimeValue extends AwsClientBuilder> builder,
+ BeanContainer beanContainer,
+ ShutdownContext shutdown) {
+ SesClientProducer producer = beanContainer.instance(SesClientProducer.class);
+ producer.setAsyncConfiguredBuilder((SesAsyncClientBuilder) builder.getValue());
+ shutdown.addShutdownTask(producer::destroy);
+ return new RuntimeValue<>(producer.asyncClient());
+ }
+}
diff --git a/extensions/amazon-services/ses/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/amazon-services/ses/runtime/src/main/resources/META-INF/quarkus-extension.yaml
new file mode 100644
index 0000000000000..6d675841c165e
--- /dev/null
+++ b/extensions/amazon-services/ses/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -0,0 +1,11 @@
+---
+name: "Amazon SES"
+metadata:
+ keywords:
+ - "ses"
+ - "aws"
+ - "amazon"
+ guide: "https://quarkus.io/guides/amazon-ses"
+ categories:
+ - "data"
+ status: "preview"
diff --git a/integration-tests/amazon-services/pom.xml b/integration-tests/amazon-services/pom.xml
index 32091c935f506..5d3e59c6aa35f 100644
--- a/integration-tests/amazon-services/pom.xml
+++ b/integration-tests/amazon-services/pom.xml
@@ -19,6 +19,8 @@
http://localhost:8008
http://localhost:8009
http://localhost:8010
+ http://localhost:8011
+ http://localhost:8012
@@ -50,6 +52,14 @@
io.quarkus
quarkus-amazon-sqs
+
+ io.quarkus
+ quarkus-amazon-ses
+
+
+ io.quarkus
+ quarkus-amazon-kms
+
software.amazon.awssdk
netty-nio-client
@@ -204,7 +214,7 @@
aws-local-stack
- s3,dynamodb,sns,sqs
+ s3,dynamodb,sns,sqs,kms,ses
0
@@ -212,6 +222,8 @@
8008:4572
8009:4575
8010:4576
+ 8011:4599
+ 8012:4566
diff --git a/integration-tests/amazon-services/src/main/java/io/quarkus/it/amazon/kms/KmsResource.java b/integration-tests/amazon-services/src/main/java/io/quarkus/it/amazon/kms/KmsResource.java
new file mode 100644
index 0000000000000..cde57e8e20748
--- /dev/null
+++ b/integration-tests/amazon-services/src/main/java/io/quarkus/it/amazon/kms/KmsResource.java
@@ -0,0 +1,76 @@
+package io.quarkus.it.amazon.kms;
+
+import static javax.ws.rs.core.MediaType.TEXT_PLAIN;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+import javax.inject.Inject;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+import org.jboss.logging.Logger;
+
+import software.amazon.awssdk.core.BytesWrapper;
+import software.amazon.awssdk.core.SdkBytes;
+import software.amazon.awssdk.services.kms.KmsAsyncClient;
+import software.amazon.awssdk.services.kms.KmsClient;
+import software.amazon.awssdk.services.kms.model.DataKeySpec;
+import software.amazon.awssdk.services.kms.model.DecryptResponse;
+import software.amazon.awssdk.services.kms.model.EncryptResponse;
+import software.amazon.awssdk.services.kms.model.GenerateDataKeyResponse;
+
+@Path("/kms")
+public class KmsResource {
+
+ private static final Logger LOG = Logger.getLogger(KmsResource.class);
+ public final static String TEXT = "Quarkus is awsome";
+
+ @Inject
+ KmsClient kmsClient;
+
+ @Inject
+ KmsAsyncClient kmsAsyncClient;
+
+ @GET
+ @Path("sync")
+ @Produces(TEXT_PLAIN)
+ public String testSync() {
+ LOG.info("Testing Sync KMS client");
+ //create customer master key and its data
+ String masterKeyId = kmsClient.createKey().keyMetadata().keyId();
+ kmsClient.generateDataKey(r -> r.keyId(masterKeyId).keySpec(DataKeySpec.AES_256));
+
+ //encrypt data
+ SdkBytes encryptedData = kmsClient.encrypt(r -> r.keyId(masterKeyId).plaintext(SdkBytes.fromUtf8String(TEXT)))
+ .ciphertextBlob();
+
+ //Decrypt data
+ return kmsClient.decrypt(r -> r.ciphertextBlob(encryptedData).keyId(masterKeyId)).plaintext()
+ .asUtf8String();
+ }
+
+ @GET
+ @Path("async")
+ @Produces(TEXT_PLAIN)
+ public CompletionStage testAsync() {
+ LOG.info("Testing Async KMS client");
+ SdkBytes textToEncrypt = SdkBytes.fromUtf8String(TEXT);
+
+ //Create master key
+ CompletableFuture masterKeyId = kmsAsyncClient.createKey()
+ .thenApply(resp -> resp.keyMetadata().keyId())
+ .thenCompose(keyId -> kmsAsyncClient.generateDataKey(req -> req.keyId(keyId).keySpec(DataKeySpec.AES_256)))
+ .thenApply(GenerateDataKeyResponse::keyId);
+
+ //Encrypt & Decrypt
+ return masterKeyId
+ .thenCompose(keyId -> kmsAsyncClient.encrypt(req -> req.keyId(keyId).plaintext(textToEncrypt))
+ .thenApply(EncryptResponse::ciphertextBlob)
+ .thenCompose(encryptedBytes -> kmsAsyncClient
+ .decrypt(req -> req.ciphertextBlob(encryptedBytes).keyId(keyId)))
+ .thenApply(DecryptResponse::plaintext)
+ .thenApply(BytesWrapper::asUtf8String));
+ }
+}
diff --git a/integration-tests/amazon-services/src/main/java/io/quarkus/it/amazon/ses/SesResource.java b/integration-tests/amazon-services/src/main/java/io/quarkus/it/amazon/ses/SesResource.java
new file mode 100644
index 0000000000000..b464a82edf423
--- /dev/null
+++ b/integration-tests/amazon-services/src/main/java/io/quarkus/it/amazon/ses/SesResource.java
@@ -0,0 +1,75 @@
+package io.quarkus.it.amazon.ses;
+
+import static javax.ws.rs.core.MediaType.TEXT_PLAIN;
+
+import java.util.concurrent.CompletionStage;
+import java.util.function.Consumer;
+
+import javax.inject.Inject;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+import org.jboss.logging.Logger;
+
+import software.amazon.awssdk.services.ses.SesAsyncClient;
+import software.amazon.awssdk.services.ses.SesClient;
+import software.amazon.awssdk.services.ses.model.SendEmailRequest.Builder;
+import software.amazon.awssdk.services.ses.model.SendEmailResponse;
+
+@Path("/ses")
+public class SesResource {
+
+ private static final Logger LOG = Logger.getLogger(SesResource.class);
+ public final static String EMAIL_TXT_BODY = "Quarkus is awsome";
+ public final static String EMAIL_SUBJECT = "Quarkus";
+ public final static String SYNC_FROM = "sync-sender@example.com";
+ public final static String SYNC_TO = "sync-recipient@example.com";
+ public final static String ASYNC_FROM = "async-sender@example.com";
+ public final static String ASYNC_TO = "async-recipient@example.com";
+
+ @Inject
+ SesClient sesClient;
+
+ @Inject
+ SesAsyncClient sesAsyncClient;
+
+ @GET
+ @Path("sync")
+ @Produces(TEXT_PLAIN)
+ public String testSync() {
+ LOG.info("Testing Sync SES client");
+ //Verify email recipients
+ sesClient.verifyEmailIdentity(email -> email.emailAddress(SYNC_TO));
+ sesClient.verifyEmailIdentity(email -> email.emailAddress(SYNC_FROM));
+ //Send email
+ SendEmailResponse sendEmailResponse = null;
+ try {
+ sendEmailResponse = sesClient.sendEmail(emailRequest());
+ } catch (Exception ex) {
+ LOG.error("Error sending email", ex);
+ }
+ return sendEmailResponse.messageId();
+ }
+
+ @GET
+ @Path("async")
+ @Produces(TEXT_PLAIN)
+ public CompletionStage testAsync() {
+ LOG.info("Testing Async SES client");
+
+ return sesAsyncClient.verifyEmailIdentity(email -> email.emailAddress(ASYNC_TO))
+ .thenCompose(r -> sesAsyncClient.verifyEmailIdentity(email -> email.emailAddress(ASYNC_FROM)))
+ .thenCompose(r -> sesAsyncClient.sendEmail(emailRequest()))
+ .thenApply(SendEmailResponse::messageId);
+ }
+
+ private Consumer emailRequest() {
+ return req -> req
+ .destination(dest -> dest.toAddresses(SYNC_TO))
+ .message(msg -> msg
+ .body(body -> body.text(text -> text.data(EMAIL_TXT_BODY)))
+ .subject(s -> s.data(EMAIL_SUBJECT)))
+ .source(SYNC_FROM);
+ }
+}
diff --git a/integration-tests/amazon-services/src/main/resources/application.properties b/integration-tests/amazon-services/src/main/resources/application.properties
index 4db2f52e93014..50dd1e5a8da8b 100644
--- a/integration-tests/amazon-services/src/main/resources/application.properties
+++ b/integration-tests/amazon-services/src/main/resources/application.properties
@@ -22,4 +22,16 @@ quarkus.sqs.endpoint-override=${sqs.url}
quarkus.sqs.aws.region=us-east-1
quarkus.sqs.aws.credentials.type=static
quarkus.sqs.aws.credentials.static-provider.access-key-id=test-key
-quarkus.sqs.aws.credentials.static-provider.secret-access-key=test-secret
\ No newline at end of file
+quarkus.sqs.aws.credentials.static-provider.secret-access-key=test-secret
+
+quarkus.kms.endpoint-override=${kms.url}
+quarkus.kms.aws.region=us-east-1
+quarkus.kms.aws.credentials.type=static
+quarkus.kms.aws.credentials.static-provider.access-key-id=test-key
+quarkus.kms.aws.credentials.static-provider.secret-access-key=test-secret
+
+quarkus.ses.endpoint-override=${ses.url}
+quarkus.ses.aws.region=us-east-1
+quarkus.ses.aws.credentials.type=static
+quarkus.ses.aws.credentials.static-provider.access-key-id=test-key
+quarkus.ses.aws.credentials.static-provider.secret-access-key=test-secret
\ No newline at end of file
diff --git a/integration-tests/amazon-services/src/test/java/io/quarkus/it/amazon/AmazonKmsITCase.java b/integration-tests/amazon-services/src/test/java/io/quarkus/it/amazon/AmazonKmsITCase.java
new file mode 100644
index 0000000000000..f95960fd511a1
--- /dev/null
+++ b/integration-tests/amazon-services/src/test/java/io/quarkus/it/amazon/AmazonKmsITCase.java
@@ -0,0 +1,8 @@
+package io.quarkus.it.amazon;
+
+import io.quarkus.test.junit.NativeImageTest;
+
+@NativeImageTest
+public class AmazonKmsITCase extends AmazonKmsTest {
+
+}
diff --git a/integration-tests/amazon-services/src/test/java/io/quarkus/it/amazon/AmazonKmsTest.java b/integration-tests/amazon-services/src/test/java/io/quarkus/it/amazon/AmazonKmsTest.java
new file mode 100644
index 0000000000000..f3d9a7acdb3e1
--- /dev/null
+++ b/integration-tests/amazon-services/src/test/java/io/quarkus/it/amazon/AmazonKmsTest.java
@@ -0,0 +1,22 @@
+package io.quarkus.it.amazon;
+
+import static org.hamcrest.Matchers.is;
+
+import org.junit.jupiter.api.Test;
+
+import io.quarkus.test.junit.QuarkusTest;
+import io.restassured.RestAssured;
+
+@QuarkusTest
+public class AmazonKmsTest {
+
+ @Test
+ public void testKmsAsync() {
+ RestAssured.when().get("/test/kms/async").then().body(is("Quarkus is awsome"));
+ }
+
+ @Test
+ public void testKmsSync() {
+ RestAssured.when().get("/test/kms/sync").then().body(is("Quarkus is awsome"));
+ }
+}
diff --git a/integration-tests/amazon-services/src/test/java/io/quarkus/it/amazon/AmazonSesITCase.java b/integration-tests/amazon-services/src/test/java/io/quarkus/it/amazon/AmazonSesITCase.java
new file mode 100644
index 0000000000000..ccb6cef4c03a3
--- /dev/null
+++ b/integration-tests/amazon-services/src/test/java/io/quarkus/it/amazon/AmazonSesITCase.java
@@ -0,0 +1,8 @@
+package io.quarkus.it.amazon;
+
+import io.quarkus.test.junit.NativeImageTest;
+
+@NativeImageTest
+public class AmazonSesITCase extends AmazonSesTest {
+
+}
diff --git a/integration-tests/amazon-services/src/test/java/io/quarkus/it/amazon/AmazonSesTest.java b/integration-tests/amazon-services/src/test/java/io/quarkus/it/amazon/AmazonSesTest.java
new file mode 100644
index 0000000000000..3dd8acd6e90ca
--- /dev/null
+++ b/integration-tests/amazon-services/src/test/java/io/quarkus/it/amazon/AmazonSesTest.java
@@ -0,0 +1,22 @@
+package io.quarkus.it.amazon;
+
+import static org.hamcrest.Matchers.any;
+
+import org.junit.jupiter.api.Test;
+
+import io.quarkus.test.junit.QuarkusTest;
+import io.restassured.RestAssured;
+
+@QuarkusTest
+public class AmazonSesTest {
+
+ @Test
+ public void testSesAsync() {
+ RestAssured.when().get("/test/ses/async").then().body(any(String.class));
+ }
+
+ @Test
+ public void testSesSync() {
+ RestAssured.when().get("/test/ses/sync").then().body(any(String.class));
+ }
+}