diff --git a/bom/application/pom.xml b/bom/application/pom.xml
index 684facb14ebbd..58fadf7b6c9cb 100644
--- a/bom/application/pom.xml
+++ b/bom/application/pom.xml
@@ -2111,6 +2111,16 @@
quarkus-amazon-alexa-deployment
${project.version}
+
+ io.quarkus
+ quarkus-amazon-secretsmanager
+ ${project.version}
+
+
+ io.quarkus
+ quarkus-amazon-secretsmanager-deployment
+ ${project.version}
+
io.quarkus
quarkus-resteasy-reactive-common
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/Feature.java b/core/deployment/src/main/java/io/quarkus/deployment/Feature.java
index 39e7f140283f1..8875deb9f7678 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/Feature.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/Feature.java
@@ -19,6 +19,7 @@ public enum Feature {
AMAZON_SES,
AMAZON_KMS,
AMAZON_SSM,
+ AMAZON_SECRETS_MANAGER,
APICURIO_REGISTRY_AVRO,
ARTEMIS_CORE,
ARTEMIS_JMS,
diff --git a/devtools/bom-descriptor-json/pom.xml b/devtools/bom-descriptor-json/pom.xml
index 539b46999d48e..ef649f972743a 100644
--- a/devtools/bom-descriptor-json/pom.xml
+++ b/devtools/bom-descriptor-json/pom.xml
@@ -253,6 +253,19 @@
+
+ io.quarkus
+ quarkus-amazon-secretsmanager
+ ${project.version}
+ pom
+ test
+
+
+ *
+ *
+
+
+
io.quarkus
quarkus-amazon-ses
diff --git a/docs/pom.xml b/docs/pom.xml
index 3993678be99c4..fd24497a190ac 100644
--- a/docs/pom.xml
+++ b/docs/pom.xml
@@ -213,6 +213,19 @@
+
+ io.quarkus
+ quarkus-amazon-secretsmanager-deployment
+ ${project.version}
+ pom
+ test
+
+
+ *
+ *
+
+
+
io.quarkus
quarkus-amazon-ses-deployment
diff --git a/docs/src/main/asciidoc/amazon-secrets-manager.adoc b/docs/src/main/asciidoc/amazon-secrets-manager.adoc
new file mode 100644
index 0000000000000..1a73e23d42d08
--- /dev/null
+++ b/docs/src/main/asciidoc/amazon-secrets-manager.adoc
@@ -0,0 +1,316 @@
+////
+This guide is maintained in the main Quarkus repository
+and pull requests should be submitted there:
+https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc
+////
+= Amazon Secrets Manager Client
+:extension-status: preview
+
+include::./attributes.adoc[]
+
+AWS Secrets Manager enables you to replace hardcoded credentials in your code, including passwords, with an API call to Secrets Manager to retrieve the secret programmatically.
+This helps ensure the secret can't be compromised by someone examining your code, because the secret no longer exists in the code.
+
+You can find more information about Secrets Manager at https://docs.aws.amazon.com/secretsmanager/[the AWS Secrets Manager website].
+
+NOTE: The Secrets Manager 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 Secrets Manager locally and on AWS.
+
+== Prerequisites
+
+To complete this guide, you need:
+
+* JDK 11+ installed with `JAVA_HOME` configured appropriately
+* an IDE
+* Apache Maven {maven-version}
+* An AWS Account to access the Secrets Manager service
+* Docker for your system to run Secrets Manager locally for testing purposes
+
+=== Set up Secrets Manager locally
+
+The easiest way to start working with Secrets Manager is to run a local instance as a container.
+
+[source,bash,subs="verbatim,attributes"]
+----
+docker run --rm --name local-secrets-manager --publish 8014:4584 -e SERVICES=secretsmanager -e START_WEB=0 -d localstack/localstack:0.11.1
+----
+This starts a Secrets Manager instance that is accessible on port `8014`.
+
+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]:
+----
+
+== Solution
+The application built here allows to store and retrieve credentials using Secrets Manager.
+
+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-secretsmanager-quickstart` {quickstarts-tree-url}/amazon-secretsmanager-quickstart[directory].
+
+== Creating the Maven project
+
+First, we need a new project. Create a new project with the following command:
+
+[source,bash,subs=attributes+]
+----
+mvn io.quarkus.platform:quarkus-maven-plugin:{quarkus-version}:create \
+ -DprojectGroupId=org.acme \
+ -DprojectArtifactId=amazon-secretsmanager-quickstart \
+ -DclassName="org.acme.secretsmanager.QuarkusSecretsManagerSyncResource" \
+ -Dpath="/sync" \
+ -Dextensions="resteasy,resteasy-jackson,amazon-secretsmanager,resteasy-mutiny"
+cd amazon-secretsmanager-quickstart
+----
+
+This command generates a Maven structure importing the RESTEasy/JAX-RS, Mutiny and Amazon Secrets Manager Client extensions.
+After this, the `amazon-secretsmanager` 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 us to store and retrieve parameters to and from SSM parameter store using a RESTful API.
+The example application will demonstrate the two programming models supported by the extension.
+
+Let's start with an abstract `org.acme.secretsmanager.QuarkusSecretsManagerResource` class to provide the common functionality we will need for both the synchronous and asynchrounous exposures.
+
+[source,java]
+----
+package org.acme.secretsmanager;
+
+import static java.lang.Boolean.TRUE;
+import static java.util.stream.Collectors.toMap;
+
+import java.util.Map;
+import java.util.stream.Collector;
+
+import javax.annotation.PostConstruct;
+
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+
+import software.amazon.awssdk.services.secretsmanager.model.CreateSecretRequest;
+import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest;
+
+public abstract class QuarkusSecretsManagerResource {
+ public static final String VERSION_STAGE = "AWSCURRENT";
+
+ @ConfigProperty(name = "secret.name") <1>
+ String secretName;
+
+ protected GetSecretValueRequest generateGetSecretValueRequest() {
+ return GetSecretValueRequest.builder() <2>
+ .secretId(secretName)
+ .versionStage(VERSION_STAGE)
+ .build();
+ }
+
+ protected CreateSecretRequest generateCreateSecretRequest(String name, String secret) {
+ return CreateSecretRequest.builder() <3>
+ .name(name)
+ .secretString(secret)
+ .build();
+ }
+
+}
+----
+
+<1> Inject a configured name under which is stored the secret
+<2> Generate a request for the credentials with the configured name
+<3> Generate a request to create a specific secret
+
+Now, we can extend the class and create the synchronous implementation in the `org.acme.ssm.QuarkusSecretsManagerSyncResource` class.
+
+[source,java]
+----
+package org.acme.secretsmanager;
+
+import java.util.Map;
+
+import javax.inject.Inject;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+
+import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
+
+@Path("/sync")
+public class QuarkusSecretsManagerSyncResource extends QuarkusSsmResource {
+
+ @Inject <1>
+ SecretsManagerClient secretsManagerClient;
+
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ public String getSecret() {
+ return secretsManagerClient.getSecretValue(generateGetSecretValueRequest()).secretString();
+ }
+
+ @POST
+ @Path("/{name}")
+ @Consumes(MediaType.TEXT_PLAIN)
+ public void createSecret(@PathParam("name") String name, String value) {
+ secretsManagerClient.createSecret(generateCreateSecretRequest(name, secret));
+ }
+
+}
+----
+
+<1> Inject the client provided by the amazon-secretsmanager extension
+
+Using the Amazon Secrets Manager SDK, we can easily store and retrieve secrets.
+
+== Configuring Secrets Manager clients
+
+Both Secrets Manager 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.secretsmanager.sync-client.type=apache
+----
+
+And add the following dependency to the application `pom.xml`:
+
+[source,xml]
+----
+
+ software.amazon.awssdk
+ apache-client
+
+
+ io.quarkus
+ quarkus-apache-httpclient
+
+----
+
+If you're going to use a local Secrets Manager instance, configure it as follows:
+
+[source,properties]
+----
+quarkus.secretsmanager.endpoint-override=http://localhost:8014 <1>
+
+quarkus.secretsmanager.aws.region=us-east-1 <2>
+quarkus.secretsmanager.aws.credentials.type=static <3>
+quarkus.secretsmanager.aws.credentials.static-provider.access-key-id=test-key
+quarkus.secretsmanager.aws.credentials.static-provider.secret-access-key=test-secret
+----
+
+<1> Override the Secret Manager client to use localstack instead of the actual AWS service
+<2> Localstack defaults to `us-east-1`
+<3> The `static` credentials provider lets you set the `access-key-id` and `secret-access-key` directly
+
+If you want to work with an AWS account, you can simply remove or comment out all Amazon SSM related properties. By default, the Secrets Manager client extension will use the `default` credentials provider chain that looks for credentials in this order:
+
+include::./amazon-credentials.adoc[]
+
+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 then be run with `java -Dparameters.path=/quarkus/is/awesome/ -jar target/quarkus-app/quarkus-run.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.secretsmanager.QuarkusSecretsManagerAsyncResource` REST resource that will be similar to our `QuarkusSecretsManagerSyncResource` but using an asynchronous programming model.
+
+[source,java]
+----
+package org.acme.secretsmanager;
+
+import java.util.Map;
+
+import javax.inject.Inject;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+
+import io.smallrye.mutiny.Uni;
+import software.amazon.awssdk.services.secretsmanager.SecretsManagerAsyncClient;
+
+@Path("/async")
+public class QuarkusSecretsManagerAsyncResource extends QuarkusSecretsManagerResource {
+
+ @Inject
+ SecretsManagerAsyncClient secretsManagerAsyncClient;
+
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ public Uni getSecret() {
+ return Uni.createFrom().completionStage(secretsManagerAsyncClient.getSecretValue(generateGetSecretValueRequest()))
+ .onItem().transform(r -> r.secretString());
+ }
+
+ @PUT
+ @Path("/{name}")
+ @Consumes(MediaType.TEXT_PLAIN)
+ public Uni createSecret(@PathParam("name") String name, String value) {
+
+ return Uni.createFrom().completionStage(secretsManagerAsyncClient.createSecret(generateCreateSecretRequest(name, secret)))
+ .onItem().transform(r -> null);
+ }
+
+}
+----
+
+Note that the `SecretsManagerAsyncClient` behaves just like the `SecretsManagerClient`, but returns `CompletionStage` objects which we use to create `Uni` instances, and then transform the emitted item.
+
+To enable the asynchronous client, we also 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-secretsmanager.adoc[opts=optional, leveloffset=+1]
diff --git a/extensions/amazon-services/pom.xml b/extensions/amazon-services/pom.xml
index 149cf3c37006b..d4f6080857f7c 100644
--- a/extensions/amazon-services/pom.xml
+++ b/extensions/amazon-services/pom.xml
@@ -24,5 +24,6 @@
ses
kms
ssm
+ secretsmanager
diff --git a/extensions/amazon-services/secretsmanager/deployment/pom.xml b/extensions/amazon-services/secretsmanager/deployment/pom.xml
new file mode 100644
index 0000000000000..1b6ba9f4e422a
--- /dev/null
+++ b/extensions/amazon-services/secretsmanager/deployment/pom.xml
@@ -0,0 +1,77 @@
+
+
+ 4.0.0
+
+
+ io.quarkus
+ quarkus-amazon-secretsmanager-parent
+ 999-SNAPSHOT
+
+
+ quarkus-amazon-secretsmanager-deployment
+ Quarkus - Amazon Services - Secrets Manager - 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-secretsmanager
+
+
+
+
+ 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/secretsmanager/deployment/src/main/java/io/quarkus/amazon/secretsmanager/deployment/SecretsManagerProcessor.java b/extensions/amazon-services/secretsmanager/deployment/src/main/java/io/quarkus/amazon/secretsmanager/deployment/SecretsManagerProcessor.java
new file mode 100644
index 0000000000000..f6930b3d83bb6
--- /dev/null
+++ b/extensions/amazon-services/secretsmanager/deployment/src/main/java/io/quarkus/amazon/secretsmanager/deployment/SecretsManagerProcessor.java
@@ -0,0 +1,140 @@
+package io.quarkus.amazon.secretsmanager.deployment;
+
+import java.util.List;
+
+import org.jboss.jandex.DotName;
+
+import io.quarkus.amazon.common.deployment.AbstractAmazonServiceProcessor;
+import io.quarkus.amazon.common.deployment.AmazonClientAsyncTransportBuildItem;
+import io.quarkus.amazon.common.deployment.AmazonClientBuildItem;
+import io.quarkus.amazon.common.deployment.AmazonClientInterceptorsPathBuildItem;
+import io.quarkus.amazon.common.deployment.AmazonClientSyncTransportBuildItem;
+import io.quarkus.amazon.common.deployment.AmazonHttpClients;
+import io.quarkus.amazon.common.runtime.AmazonClientApacheTransportRecorder;
+import io.quarkus.amazon.common.runtime.AmazonClientNettyTransportRecorder;
+import io.quarkus.amazon.common.runtime.AmazonClientRecorder;
+import io.quarkus.amazon.common.runtime.AmazonClientUrlConnectionTransportRecorder;
+import io.quarkus.amazon.secretsmanager.runtime.SecretsManagerBuildTimeConfig;
+import io.quarkus.amazon.secretsmanager.runtime.SecretsManagerClientProducer;
+import io.quarkus.amazon.secretsmanager.runtime.SecretsManagerConfig;
+import io.quarkus.amazon.secretsmanager.runtime.SecretsManagerRecorder;
+import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
+import io.quarkus.arc.deployment.BeanRegistrationPhaseBuildItem;
+import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
+import io.quarkus.deployment.Feature;
+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 software.amazon.awssdk.services.secretsmanager.SecretsManagerAsyncClient;
+import software.amazon.awssdk.services.secretsmanager.SecretsManagerAsyncClientBuilder;
+import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
+import software.amazon.awssdk.services.secretsmanager.SecretsManagerClientBuilder;
+
+public class SecretsManagerProcessor extends AbstractAmazonServiceProcessor {
+
+ SecretsManagerBuildTimeConfig buildTimeConfig;
+
+ @Override
+ protected Feature amazonServiceClientName() {
+ return Feature.AMAZON_SECRETS_MANAGER;
+ }
+
+ @Override
+ protected String configName() {
+ return "secretsmanager";
+ }
+
+ @Override
+ protected DotName syncClientName() {
+ return DotName.createSimple(SecretsManagerClient.class.getName());
+ }
+
+ @Override
+ protected DotName asyncClientName() {
+ return DotName.createSimple(SecretsManagerAsyncClient.class.getName());
+ }
+
+ @Override
+ protected String builtinInterceptorsPath() {
+ return "software/amazon/awssdk/services/secretsmanager/execution.interceptors";
+ }
+
+ @BuildStep
+ AdditionalBeanBuildItem producer() {
+ return AdditionalBeanBuildItem.unremovableOf(SecretsManagerClientProducer.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(onlyIf = AmazonHttpClients.IsAmazonApacheHttpServicePresent.class)
+ @Record(ExecutionTime.RUNTIME_INIT)
+ void setupApacheSyncTransport(List amazonClients, SecretsManagerRecorder recorder,
+ AmazonClientApacheTransportRecorder transportRecorder,
+ SecretsManagerConfig runtimeConfig, BuildProducer syncTransports) {
+
+ createApacheSyncTransportBuilder(amazonClients,
+ transportRecorder,
+ buildTimeConfig.syncClient,
+ recorder.getSyncConfig(runtimeConfig),
+ syncTransports);
+ }
+
+ @BuildStep(onlyIf = AmazonHttpClients.IsAmazonUrlConnectionHttpServicePresent.class)
+ @Record(ExecutionTime.RUNTIME_INIT)
+ void setupUrlConnectionSyncTransport(List amazonClients, SecretsManagerRecorder recorder,
+ AmazonClientUrlConnectionTransportRecorder transportRecorder,
+ SecretsManagerConfig runtimeConfig, BuildProducer syncTransports) {
+
+ createUrlConnectionSyncTransportBuilder(amazonClients,
+ transportRecorder,
+ buildTimeConfig.syncClient,
+ recorder.getSyncConfig(runtimeConfig),
+ syncTransports);
+ }
+
+ @BuildStep(onlyIf = AmazonHttpClients.IsAmazonNettyHttpServicePresent.class)
+ @Record(ExecutionTime.RUNTIME_INIT)
+ void setupNettyAsyncTransport(List amazonClients, SecretsManagerRecorder recorder,
+ AmazonClientNettyTransportRecorder transportRecorder,
+ SecretsManagerConfig runtimeConfig, BuildProducer asyncTransports) {
+
+ createNettyAsyncTransportBuilder(amazonClients,
+ transportRecorder,
+ recorder.getAsyncConfig(runtimeConfig),
+ asyncTransports);
+ }
+
+ @BuildStep
+ @Record(ExecutionTime.RUNTIME_INIT)
+ void createClientBuilders(SecretsManagerRecorder recorder,
+ AmazonClientRecorder commonRecorder,
+ SecretsManagerConfig runtimeConfig,
+ List syncTransports,
+ List asyncTransports,
+ BuildProducer syntheticBeans) {
+
+ createClientBuilders(commonRecorder,
+ recorder.getAwsConfig(runtimeConfig),
+ recorder.getSdkConfig(runtimeConfig),
+ buildTimeConfig.sdk,
+ syncTransports,
+ asyncTransports,
+ SecretsManagerClientBuilder.class,
+ (syncTransport) -> recorder.createSyncBuilder(runtimeConfig, syncTransport),
+ SecretsManagerAsyncClientBuilder.class,
+ (asyncTransport) -> recorder.createAsyncBuilder(runtimeConfig, asyncTransport),
+ syntheticBeans);
+ }
+}
diff --git a/extensions/amazon-services/secretsmanager/deployment/src/test/java/io/quarkus/amazon/secretsmanager/deployment/SecretsManagerSyncClientFullConfigTest.java b/extensions/amazon-services/secretsmanager/deployment/src/test/java/io/quarkus/amazon/secretsmanager/deployment/SecretsManagerSyncClientFullConfigTest.java
new file mode 100644
index 0000000000000..b595d2e759e3c
--- /dev/null
+++ b/extensions/amazon-services/secretsmanager/deployment/src/test/java/io/quarkus/amazon/secretsmanager/deployment/SecretsManagerSyncClientFullConfigTest.java
@@ -0,0 +1,31 @@
+package io.quarkus.amazon.secretsmanager.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.secretsmanager.SecretsManagerAsyncClient;
+import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
+
+public class SecretsManagerSyncClientFullConfigTest {
+
+ @Inject
+ SecretsManagerClient client;
+
+ @Inject
+ SecretsManagerAsyncClient 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/secretsmanager/deployment/src/test/resources/sync-urlconn-full-config.properties b/extensions/amazon-services/secretsmanager/deployment/src/test/resources/sync-urlconn-full-config.properties
new file mode 100644
index 0000000000000..ee50426870598
--- /dev/null
+++ b/extensions/amazon-services/secretsmanager/deployment/src/test/resources/sync-urlconn-full-config.properties
@@ -0,0 +1,10 @@
+quarkus.secretsmanager.endpoint-override=http://localhost:9090
+
+quarkus.secretsmanager.aws.region=us-east-1
+quarkus.secretsmanager.aws.credentials.type=static
+quarkus.secretsmanager.aws.credentials.static-provider.access-key-id=test-key
+quarkus.secretsmanager.aws.credentials.static-provider.secret-access-key=test-secret
+
+quarkus.secretsmanager.sync-client.type = url
+quarkus.secretsmanager.sync-client.connection-timeout = 0.100S
+quarkus.secretsmanager.sync-client.socket-timeout = 0.100S
\ No newline at end of file
diff --git a/extensions/amazon-services/secretsmanager/pom.xml b/extensions/amazon-services/secretsmanager/pom.xml
new file mode 100644
index 0000000000000..4cd391a62f747
--- /dev/null
+++ b/extensions/amazon-services/secretsmanager/pom.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+
+
+ io.quarkus
+ quarkus-amazon-services-parent
+ 999-SNAPSHOT
+
+
+ quarkus-amazon-secretsmanager-parent
+ Quarkus - Amazon Services - Secrets Manager
+ pom
+
+
+ runtime
+ deployment
+
+
+
diff --git a/extensions/amazon-services/secretsmanager/runtime/pom.xml b/extensions/amazon-services/secretsmanager/runtime/pom.xml
new file mode 100644
index 0000000000000..dc0aa465d4663
--- /dev/null
+++ b/extensions/amazon-services/secretsmanager/runtime/pom.xml
@@ -0,0 +1,96 @@
+
+
+ 4.0.0
+
+
+ io.quarkus
+ quarkus-amazon-secretsmanager-parent
+ 999-SNAPSHOT
+
+
+ quarkus-amazon-secretsmanager
+ Quarkus - Amazon Services - Secrets Manager - Runtime
+ Connect to Amazon Secrets Manager
+
+
+
+ io.quarkus
+ quarkus-core
+
+
+ io.quarkus
+ quarkus-arc
+
+
+ io.quarkus
+ quarkus-netty
+
+
+
+ io.quarkus
+ quarkus-amazon-common
+
+
+ software.amazon.awssdk
+ secretsmanager
+
+
+
+ software.amazon.awssdk
+ netty-nio-client
+
+
+ software.amazon.awssdk
+ url-connection-client
+
+
+ software.amazon.awssdk
+ apache-client
+
+
+
+
+ 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/secretsmanager/runtime/src/main/java/io/quarkus/amazon/secretsmanager/runtime/SecretsManagerBuildTimeConfig.java b/extensions/amazon-services/secretsmanager/runtime/src/main/java/io/quarkus/amazon/secretsmanager/runtime/SecretsManagerBuildTimeConfig.java
new file mode 100644
index 0000000000000..773446cdb500a
--- /dev/null
+++ b/extensions/amazon-services/secretsmanager/runtime/src/main/java/io/quarkus/amazon/secretsmanager/runtime/SecretsManagerBuildTimeConfig.java
@@ -0,0 +1,26 @@
+package io.quarkus.amazon.secretsmanager.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 Secrets Manager build time configuration
+ */
+@ConfigRoot(name = "secretsmanager", phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED)
+public class SecretsManagerBuildTimeConfig {
+
+ /**
+ * SDK client configurations for AWS Secrets Manager client
+ */
+ @ConfigItem(name = ConfigItem.PARENT)
+ public SdkBuildTimeConfig sdk;
+
+ /**
+ * Sync HTTP transport configuration for Amazon Secrets Manager client
+ */
+ @ConfigItem
+ public SyncHttpClientBuildTimeConfig syncClient;
+}
diff --git a/extensions/amazon-services/secretsmanager/runtime/src/main/java/io/quarkus/amazon/secretsmanager/runtime/SecretsManagerClientProducer.java b/extensions/amazon-services/secretsmanager/runtime/src/main/java/io/quarkus/amazon/secretsmanager/runtime/SecretsManagerClientProducer.java
new file mode 100644
index 0000000000000..cb25405aeab41
--- /dev/null
+++ b/extensions/amazon-services/secretsmanager/runtime/src/main/java/io/quarkus/amazon/secretsmanager/runtime/SecretsManagerClientProducer.java
@@ -0,0 +1,51 @@
+package io.quarkus.amazon.secretsmanager.runtime;
+
+import javax.annotation.PreDestroy;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.Instance;
+import javax.enterprise.inject.Produces;
+
+import software.amazon.awssdk.services.secretsmanager.SecretsManagerAsyncClient;
+import software.amazon.awssdk.services.secretsmanager.SecretsManagerAsyncClientBuilder;
+import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
+import software.amazon.awssdk.services.secretsmanager.SecretsManagerClientBuilder;
+
+@ApplicationScoped
+public class SecretsManagerClientProducer {
+ private final SecretsManagerClient syncClient;
+ private final SecretsManagerAsyncClient asyncClient;
+
+ SecretsManagerClientProducer(Instance syncClientBuilderInstance,
+ Instance asyncClientBuilderInstance) {
+ this.syncClient = syncClientBuilderInstance.isResolvable() ? syncClientBuilderInstance.get().build() : null;
+ this.asyncClient = asyncClientBuilderInstance.isResolvable() ? asyncClientBuilderInstance.get().build() : null;
+ }
+
+ @Produces
+ @ApplicationScoped
+ public SecretsManagerClient client() {
+ if (syncClient == null) {
+ throw new IllegalStateException("The SecretsManagerClient is required but has not been detected/configured.");
+ }
+ return syncClient;
+ }
+
+ @Produces
+ @ApplicationScoped
+ public SecretsManagerAsyncClient asyncClient() {
+ if (asyncClient == null) {
+ throw new IllegalStateException("The SecretsManagerAsyncClient is required but has not been detected/configured.");
+ }
+ return asyncClient;
+ }
+
+ @PreDestroy
+ public void destroy() {
+ if (syncClient != null) {
+ syncClient.close();
+ }
+ if (asyncClient != null) {
+ asyncClient.close();
+ }
+ }
+}
diff --git a/extensions/amazon-services/secretsmanager/runtime/src/main/java/io/quarkus/amazon/secretsmanager/runtime/SecretsManagerConfig.java b/extensions/amazon-services/secretsmanager/runtime/src/main/java/io/quarkus/amazon/secretsmanager/runtime/SecretsManagerConfig.java
new file mode 100644
index 0000000000000..8c454542322a1
--- /dev/null
+++ b/extensions/amazon-services/secretsmanager/runtime/src/main/java/io/quarkus/amazon/secretsmanager/runtime/SecretsManagerConfig.java
@@ -0,0 +1,42 @@
+package io.quarkus.amazon.secretsmanager.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 = "secretsmanager", phase = ConfigPhase.RUN_TIME)
+public class SecretsManagerConfig {
+
+ /**
+ * 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/secretsmanager/runtime/src/main/java/io/quarkus/amazon/secretsmanager/runtime/SecretsManagerRecorder.java b/extensions/amazon-services/secretsmanager/runtime/src/main/java/io/quarkus/amazon/secretsmanager/runtime/SecretsManagerRecorder.java
new file mode 100644
index 0000000000000..4a19099757ea3
--- /dev/null
+++ b/extensions/amazon-services/secretsmanager/runtime/src/main/java/io/quarkus/amazon/secretsmanager/runtime/SecretsManagerRecorder.java
@@ -0,0 +1,54 @@
+package io.quarkus.amazon.secretsmanager.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.RuntimeValue;
+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.secretsmanager.SecretsManagerAsyncClient;
+import software.amazon.awssdk.services.secretsmanager.SecretsManagerAsyncClientBuilder;
+import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
+import software.amazon.awssdk.services.secretsmanager.SecretsManagerClientBuilder;
+
+@Recorder
+public class SecretsManagerRecorder {
+
+ public RuntimeValue getSyncConfig(SecretsManagerConfig config) {
+ return new RuntimeValue<>(config.syncClient);
+ }
+
+ public RuntimeValue getAsyncConfig(SecretsManagerConfig config) {
+ return new RuntimeValue<>(config.asyncClient);
+ }
+
+ public RuntimeValue getAwsConfig(SecretsManagerConfig config) {
+ return new RuntimeValue<>(config.aws);
+ }
+
+ public RuntimeValue getSdkConfig(SecretsManagerConfig config) {
+ return new RuntimeValue<>(config.sdk);
+ }
+
+ public RuntimeValue createSyncBuilder(SecretsManagerConfig config,
+ RuntimeValue transport) {
+ SecretsManagerClientBuilder builder = SecretsManagerClient.builder();
+ if (transport != null) {
+ builder.httpClientBuilder(transport.getValue());
+ }
+ return new RuntimeValue<>(builder);
+ }
+
+ public RuntimeValue createAsyncBuilder(SecretsManagerConfig config,
+ RuntimeValue transport) {
+
+ SecretsManagerAsyncClientBuilder builder = SecretsManagerAsyncClient.builder();
+ if (transport != null) {
+ builder.httpClientBuilder(transport.getValue());
+ }
+ return new RuntimeValue<>(builder);
+ }
+}
diff --git a/extensions/amazon-services/secretsmanager/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/amazon-services/secretsmanager/runtime/src/main/resources/META-INF/quarkus-extension.yaml
new file mode 100644
index 0000000000000..a49c80f350b69
--- /dev/null
+++ b/extensions/amazon-services/secretsmanager/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -0,0 +1,14 @@
+---
+artifact: ${project.groupId}:${project.artifactId}:${project.version}
+name: "Amazon Secrets Manager"
+metadata:
+ keywords:
+ - "secretsmanager"
+ - "aws"
+ - "amazon"
+ guide: "https://quarkus.io/guides/amazon-secretsmanager"
+ categories:
+ - "data"
+ status: "preview"
+ config:
+ - "quarkus.secretsmanager."
diff --git a/integration-tests/amazon-services/pom.xml b/integration-tests/amazon-services/pom.xml
index 8eb69b689a76f..6f20e89f7a5eb 100644
--- a/integration-tests/amazon-services/pom.xml
+++ b/integration-tests/amazon-services/pom.xml
@@ -22,6 +22,7 @@
http://localhost:8012
http://localhost:8013
http://localhost:8014
+ http://localhost:8015
@@ -69,6 +70,10 @@
io.quarkus
quarkus-amazon-ssm
+
+ io.quarkus
+ quarkus-amazon-secretsmanager
+
software.amazon.awssdk
netty-nio-client
@@ -201,6 +206,19 @@
+
+ io.quarkus
+ quarkus-amazon-secretsmanager-deployment
+ ${project.version}
+ pom
+ test
+
+
+ *
+ *
+
+
+
io.quarkus
quarkus-arc-deployment
@@ -321,7 +339,7 @@
aws-local-stack
- s3,dynamodb,sns,sqs,kms,ssm,ses
+ s3,dynamodb,sns,sqs,kms,ssm,ses,secretsmanager
0
@@ -333,6 +351,7 @@
8012:4566
8013:4593
8014:4583
+ 8015:4584
diff --git a/integration-tests/amazon-services/src/main/java/io/quarkus/it/amazon/secretsmanager/SecretsManagerResource.java b/integration-tests/amazon-services/src/main/java/io/quarkus/it/amazon/secretsmanager/SecretsManagerResource.java
new file mode 100644
index 0000000000000..1d3ea2d146bc6
--- /dev/null
+++ b/integration-tests/amazon-services/src/main/java/io/quarkus/it/amazon/secretsmanager/SecretsManagerResource.java
@@ -0,0 +1,54 @@
+package io.quarkus.it.amazon.secretsmanager;
+
+import static javax.ws.rs.core.MediaType.TEXT_PLAIN;
+
+import java.util.UUID;
+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.services.secretsmanager.SecretsManagerAsyncClient;
+import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
+import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueResponse;
+
+@Path("/secretsmanager")
+public class SecretsManagerResource {
+
+ private static final Logger LOG = Logger.getLogger(SecretsManagerResource.class);
+ public final static String TEXT = "Quarkus is awsome";
+ private static final String SYNC_PARAM = "quarkus/sync-" + UUID.randomUUID().toString();
+ private static final String ASYNC_PARAM = "quarkus/async-" + UUID.randomUUID().toString();
+
+ @Inject
+ SecretsManagerClient secretsManagerClient;
+
+ @Inject
+ SecretsManagerAsyncClient secretsManagerAsyncClient;
+
+ @GET
+ @Path("sync")
+ @Produces(TEXT_PLAIN)
+ public String testSync() {
+ LOG.info("Testing Sync Secrets Manager client with secret name: " + SYNC_PARAM);
+ //Put parameter
+ secretsManagerClient.createSecret(r -> r.name(SYNC_PARAM).secretString(TEXT));
+ //Get parameter
+ return secretsManagerClient.getSecretValue(r -> r.secretId(SYNC_PARAM)).secretString();
+ }
+
+ @GET
+ @Path("async")
+ @Produces(TEXT_PLAIN)
+ public CompletionStage testAsync() {
+ LOG.info("Testing Async SSM client with parameter: " + ASYNC_PARAM);
+ //Put and get parameter
+ return secretsManagerAsyncClient.createSecret(r -> r.name(ASYNC_PARAM).secretString(TEXT))
+ .thenCompose(result -> secretsManagerAsyncClient.getSecretValue(r -> r.secretId(ASYNC_PARAM)))
+ .thenApply(GetSecretValueResponse::secretString);
+ }
+}
diff --git a/integration-tests/amazon-services/src/main/resources/application.properties b/integration-tests/amazon-services/src/main/resources/application.properties
index 9bdb8d9282aa5..81164394640ae 100644
--- a/integration-tests/amazon-services/src/main/resources/application.properties
+++ b/integration-tests/amazon-services/src/main/resources/application.properties
@@ -48,4 +48,10 @@ quarkus.iam.endpoint-override=${iam.url}
quarkus.iam.aws.region=us-east-1
quarkus.iam.aws.credentials.type=static
quarkus.iam.aws.credentials.static-provider.access-key-id=test-key
-quarkus.iam.aws.credentials.static-provider.secret-access-key=test-secret
\ No newline at end of file
+quarkus.iam.aws.credentials.static-provider.secret-access-key=test-secret
+
+quarkus.secretsmanager.endpoint-override=${secretsmanager.url}
+quarkus.secretsmanager.aws.region=us-east-1
+quarkus.secretsmanager.aws.credentials.type=static
+quarkus.secretsmanager.aws.credentials.static-provider.access-key-id=test-key
+quarkus.secretsmanager.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/AmazonSecretsManagerITCase.java b/integration-tests/amazon-services/src/test/java/io/quarkus/it/amazon/AmazonSecretsManagerITCase.java
new file mode 100644
index 0000000000000..7f05a348fa4b3
--- /dev/null
+++ b/integration-tests/amazon-services/src/test/java/io/quarkus/it/amazon/AmazonSecretsManagerITCase.java
@@ -0,0 +1,8 @@
+package io.quarkus.it.amazon;
+
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+
+@QuarkusIntegrationTest
+public class AmazonSecretsManagerITCase extends AmazonSecretsManagerTest {
+
+}
diff --git a/integration-tests/amazon-services/src/test/java/io/quarkus/it/amazon/AmazonSecretsManagerTest.java b/integration-tests/amazon-services/src/test/java/io/quarkus/it/amazon/AmazonSecretsManagerTest.java
new file mode 100644
index 0000000000000..ab6cad2016625
--- /dev/null
+++ b/integration-tests/amazon-services/src/test/java/io/quarkus/it/amazon/AmazonSecretsManagerTest.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 AmazonSecretsManagerTest {
+
+ @Test
+ public void testSecretsManagerAsync() {
+ RestAssured.when().get("/test/secretsmanager/async").then().body(is("Quarkus is awsome"));
+ }
+
+ @Test
+ public void testSecretsManagerSync() {
+ RestAssured.when().get("/test/secretsmanager/sync").then().body(is("Quarkus is awsome"));
+ }
+}