Skip to content

Commit d8b74e9

Browse files
committed
DynamoDB configurations update:
- Refactored configuration structure - broken down config into dynamodb, AWS, SDK & HTTP client groups - Added config of TLS Managers for sync/async clients - Added config of HTTP Proxy for async client - Added support for SDK client interceptors
1 parent 385b68f commit d8b74e9

30 files changed

+955
-683
lines changed

bom/runtime/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1395,7 +1395,7 @@
13951395
<artifactId>aesh</artifactId>
13961396
<version>${aesh.version}</version>
13971397
</dependency>
1398-
1398+
13991399
<dependency>
14001400
<groupId>com.oracle.substratevm</groupId>
14011401
<artifactId>svm</artifactId>

docs/src/main/asciidoc/dynamodb-guide.adoc

Lines changed: 13 additions & 312 deletions
Large diffs are not rendered by default.

extensions/amazon-dynamodb/deployment/src/main/java/io/quarkus/dynamodb/deployment/DynamodbProcessor.java

Lines changed: 138 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package io.quarkus.dynamodb.deployment;
22

33
import java.net.URI;
4+
import java.util.Arrays;
5+
import java.util.List;
6+
import java.util.stream.Collectors;
47

58
import org.jboss.jandex.DotName;
69
import org.jboss.jandex.Type;
@@ -14,19 +17,25 @@
1417
import io.quarkus.deployment.annotations.BuildStep;
1518
import io.quarkus.deployment.annotations.ExecutionTime;
1619
import io.quarkus.deployment.annotations.Record;
20+
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
1721
import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem;
1822
import io.quarkus.deployment.builditem.FeatureBuildItem;
1923
import io.quarkus.deployment.builditem.JniBuildItem;
2024
import io.quarkus.deployment.builditem.ShutdownContextBuildItem;
25+
import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem;
2126
import io.quarkus.deployment.builditem.substrate.ServiceProviderBuildItem;
2227
import io.quarkus.deployment.builditem.substrate.SubstrateProxyDefinitionBuildItem;
28+
import io.quarkus.deployment.builditem.substrate.SubstrateResourceBuildItem;
2329
import io.quarkus.deployment.configuration.ConfigurationError;
24-
import io.quarkus.dynamodb.runtime.AwsApacheHttpClientConfig;
30+
import io.quarkus.dynamodb.runtime.ApacheHttpClientConfig;
2531
import io.quarkus.dynamodb.runtime.AwsCredentialsProviderType;
26-
import io.quarkus.dynamodb.runtime.AwsNettyNioAsyncHttpClientConfig;
2732
import io.quarkus.dynamodb.runtime.DynamodbClientProducer;
2833
import io.quarkus.dynamodb.runtime.DynamodbConfig;
2934
import io.quarkus.dynamodb.runtime.DynamodbRecorder;
35+
import io.quarkus.dynamodb.runtime.NettyHttpClientConfig;
36+
import io.quarkus.dynamodb.runtime.TlsManagersProviderConfig;
37+
import io.quarkus.dynamodb.runtime.TlsManagersProviderType;
38+
import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
3039
import software.amazon.awssdk.http.SdkHttpService;
3140
import software.amazon.awssdk.http.apache.ApacheSdkHttpService;
3241
import software.amazon.awssdk.http.async.SdkAsyncHttpService;
@@ -38,6 +47,11 @@
3847
public class DynamodbProcessor {
3948
public static final String AWS_SDK_APPLICATION_ARCHIVE_MARKERS = "software/amazon/awssdk";
4049

50+
private static final List<String> INTERCEPTOR_PATHS = Arrays.asList(
51+
"software/amazon/awssdk/global/handlers/execution.interceptors",
52+
"software/amazon/awssdk/services/dynamodb/execution.interceptors");
53+
54+
private static final DotName EXECUTION_INTERCEPTOR_NAME = DotName.createSimple(ExecutionInterceptor.class.getName());
4155
private static final DotName SYNC_CLIENT_NAME = DotName.createSimple(DynamoDbClient.class.getName());
4256
private static final DotName ASYNC_CLIENT_NAME = DotName.createSimple(DynamoDbAsyncClient.class.getName());
4357

@@ -49,17 +63,29 @@ JniBuildItem jni() {
4963
}
5064

5165
@BuildStep(applicationArchiveMarkers = { AWS_SDK_APPLICATION_ARCHIVE_MARKERS })
52-
void setup(BuildProducer<ExtensionSslNativeSupportBuildItem> extensionSslNativeSupport,
53-
BuildProducer<ServiceProviderBuildItem> serviceProvider,
66+
void setup(CombinedIndexBuildItem combinedIndexBuildItem,
67+
BuildProducer<ExtensionSslNativeSupportBuildItem> extensionSslNativeSupport,
68+
BuildProducer<ReflectiveClassBuildItem> reflectiveClasses,
5469
BuildProducer<FeatureBuildItem> feature,
55-
BuildProducer<AdditionalBeanBuildItem> additionalBeans) {
70+
BuildProducer<AdditionalBeanBuildItem> additionalBeans,
71+
BuildProducer<SubstrateResourceBuildItem> resource) {
5672

5773
feature.produce(new FeatureBuildItem(FeatureBuildItem.DYNAMODB));
5874

5975
// Indicates that this extension would like the SSL support to be enabled
6076
extensionSslNativeSupport.produce(new ExtensionSslNativeSupportBuildItem(FeatureBuildItem.DYNAMODB));
6177

62-
checkConfig(config);
78+
INTERCEPTOR_PATHS.stream().forEach(path -> resource.produce(new SubstrateResourceBuildItem(path)));
79+
80+
List<String> knownInterceptorImpls = combinedIndexBuildItem.getIndex()
81+
.getAllKnownImplementors(EXECUTION_INTERCEPTOR_NAME)
82+
.stream()
83+
.map(c -> c.name().toString()).collect(Collectors.toList());
84+
85+
checkConfig(config, knownInterceptorImpls);
86+
87+
reflectiveClasses.produce(new ReflectiveClassBuildItem(false, false,
88+
knownInterceptorImpls.toArray(new String[knownInterceptorImpls.size()])));
6389

6490
additionalBeans.produce(AdditionalBeanBuildItem.unremovableOf(DynamodbClientProducer.class));
6591
}
@@ -122,30 +148,43 @@ void buildClients(DynamodbClientBuildItem clientBuildItem, DynamodbRecorder reco
122148
}
123149
}
124150

125-
private static void checkConfig(DynamodbConfig config) {
126-
if (config.endpointOverride.isPresent()) {
127-
URI endpointOverride = config.endpointOverride.get();
128-
if (StringUtils.isBlank(endpointOverride.getScheme())) {
129-
throw new ConfigurationError(
130-
String.format("quarkus.dynamodb.endpoint-override (%s) - scheme must be specified",
131-
endpointOverride.toString()));
151+
private static void checkConfig(DynamodbConfig config, List<String> knownInterceptorImpls) {
152+
if (config.sdk != null) {
153+
if (config.sdk.endpointOverride.isPresent()) {
154+
URI endpointOverride = config.sdk.endpointOverride.get();
155+
if (StringUtils.isBlank(endpointOverride.getScheme())) {
156+
throw new ConfigurationError(
157+
String.format("quarkus.dynamodb.sdk.endpoint-override (%s) - scheme must be specified",
158+
endpointOverride.toString()));
159+
}
132160
}
161+
config.sdk.interceptors.stream().forEach(interceptorClass -> {
162+
if (!knownInterceptorImpls.contains(interceptorClass.getName())) {
163+
throw new ConfigurationError(
164+
String.format(
165+
"quarkus.dynamodb.sdk.interceptors (%s) - must list only existing implementations of software.amazon.awssdk.core.interceptor.ExecutionInterceptor",
166+
config.sdk.interceptors.toString()));
167+
}
168+
});
133169
}
134170

135-
if (config.credentials.type == AwsCredentialsProviderType.STATIC) {
136-
if (StringUtils.isBlank(config.credentials.staticProvider.accessKeyId)
137-
|| StringUtils.isBlank(config.credentials.staticProvider.secretAccessKey)) {
138-
throw new ConfigurationError(
139-
"quarkus.dynamodb.credentials.static-provider.access-key-id and "
140-
+ "quarkus.dynamodb.credentials.static-provider.secret-access-key cannot be empty if STATIC credentials provider used.");
171+
if (config.aws != null) {
172+
if (config.aws.credentials.type == AwsCredentialsProviderType.STATIC) {
173+
if (StringUtils.isBlank(config.aws.credentials.staticProvider.accessKeyId)
174+
|| StringUtils.isBlank(config.aws.credentials.staticProvider.secretAccessKey)) {
175+
throw new ConfigurationError(
176+
"quarkus.dynamodb.aws.credentials.static-provider.access-key-id and "
177+
+ "quarkus.dynamodb.aws.credentials.static-provider.secret-access-key cannot be empty if STATIC credentials provider used.");
178+
}
141179
}
142-
}
143-
if (config.credentials.type == AwsCredentialsProviderType.PROCESS) {
144-
if (StringUtils.isBlank(config.credentials.processProvider.command)) {
145-
throw new ConfigurationError(
146-
"quarkus.dynamodb.credentials.process-provider.command cannot be empty if PROCESS credentials provider used.");
180+
if (config.aws.credentials.type == AwsCredentialsProviderType.PROCESS) {
181+
if (StringUtils.isBlank(config.aws.credentials.processProvider.command)) {
182+
throw new ConfigurationError(
183+
"quarkus.dynamodb.aws.credentials.process-provider.command cannot be empty if PROCESS credentials provider used.");
184+
}
147185
}
148186
}
187+
149188
if (config.syncClient != null) {
150189
checkSyncClientConfig(config.syncClient);
151190
}
@@ -154,45 +193,20 @@ private static void checkConfig(DynamodbConfig config) {
154193
}
155194
}
156195

157-
private static void checkSyncClientConfig(AwsApacheHttpClientConfig syncClient) {
196+
private static void checkSyncClientConfig(ApacheHttpClientConfig syncClient) {
158197
if (syncClient.maxConnections.isPresent() && syncClient.maxConnections.getAsInt() <= 0) {
159198
throw new ConfigurationError("quarkus.dynamodb.sync-client.max-connections may not be negative or zero.");
160199
}
161200
if (syncClient.proxy != null && syncClient.proxy.enabled) {
162201
URI proxyEndpoint = syncClient.proxy.endpoint;
163202
if (proxyEndpoint != null) {
164-
if (StringUtils.isBlank(proxyEndpoint.getScheme())) {
165-
throw new ConfigurationError(
166-
String.format("quarkus.dynamodb.sync-client.proxy.endpoint (%s) - scheme must be specified",
167-
proxyEndpoint.toString()));
168-
}
169-
if (StringUtils.isNotBlank(proxyEndpoint.getUserInfo())) {
170-
throw new ConfigurationError(
171-
String.format(
172-
"quarkus.dynamodb.sync-client.proxy.endpoint (%s) - user info is not supported.",
173-
proxyEndpoint.toString()));
174-
}
175-
if (StringUtils.isNotBlank(proxyEndpoint.getPath())) {
176-
throw new ConfigurationError(
177-
String.format("quarkus.dynamodb.sync-client.proxy.endpoint (%s) - path is not supported.",
178-
proxyEndpoint.toString()));
179-
}
180-
if (StringUtils.isNotBlank(proxyEndpoint.getQuery())) {
181-
throw new ConfigurationError(
182-
String.format("quarkus.dynamodb.sync-client.proxy.endpoint (%s) - query is not supported.",
183-
proxyEndpoint.toString()));
184-
}
185-
if (StringUtils.isNotBlank(proxyEndpoint.getFragment())) {
186-
throw new ConfigurationError(
187-
String.format(
188-
"quarkus.dynamodb.sync-client.proxy.endpoint (%s) - fragment is not supported.",
189-
proxyEndpoint.toString()));
190-
}
203+
validateProxyEndpoint(proxyEndpoint, "sync");
191204
}
192205
}
206+
validateTlsManagersProvider(syncClient.tlsManagersProvider, "sync");
193207
}
194208

195-
private static void checkAsyncClientConfig(AwsNettyNioAsyncHttpClientConfig asyncClient) {
209+
private static void checkAsyncClientConfig(NettyHttpClientConfig asyncClient) {
196210
if (asyncClient.maxConcurrency.isPresent() && asyncClient.maxConcurrency.get() <= 0) {
197211
throw new ConfigurationError("quarkus.dynamodb.async-client.max-concurrency may not be negative or zero.");
198212
}
@@ -211,5 +225,76 @@ private static void checkAsyncClientConfig(AwsNettyNioAsyncHttpClientConfig asyn
211225
"quarkus.dynamodb.async-client.event-loop.number-of-threads may not be negative or zero.");
212226
}
213227
}
228+
if (asyncClient.proxy != null && asyncClient.proxy.enabled) {
229+
URI proxyEndpoint = asyncClient.proxy.endpoint;
230+
if (proxyEndpoint != null) {
231+
validateProxyEndpoint(proxyEndpoint, "async");
232+
}
233+
}
234+
validateTlsManagersProvider(asyncClient.tlsManagersProvider, "async");
235+
}
236+
237+
private static void validateProxyEndpoint(URI endpoint, String clientType) {
238+
if (StringUtils.isBlank(endpoint.getScheme())) {
239+
throw new ConfigurationError(
240+
String.format("quarkus.dynamodb.%s-client.proxy.endpoint (%s) - scheme must be specified",
241+
clientType, endpoint.toString()));
242+
}
243+
if (StringUtils.isBlank(endpoint.getHost())) {
244+
throw new ConfigurationError(
245+
String.format("quarkus.dynamodb.%s-client.proxy.endpoint (%s) - host must be specified",
246+
clientType, endpoint.toString()));
247+
}
248+
if (StringUtils.isNotBlank(endpoint.getUserInfo())) {
249+
throw new ConfigurationError(
250+
String.format("quarkus.dynamodb.%s-client.proxy.endpoint (%s) - user info is not supported.",
251+
clientType, endpoint.toString()));
252+
}
253+
if (StringUtils.isNotBlank(endpoint.getPath())) {
254+
throw new ConfigurationError(
255+
String.format("quarkus.dynamodb.%s-client.proxy.endpoint (%s) - path is not supported.",
256+
clientType, endpoint.toString()));
257+
}
258+
if (StringUtils.isNotBlank(endpoint.getQuery())) {
259+
throw new ConfigurationError(
260+
String.format("quarkus.dynamodb.%s-client.proxy.endpoint (%s) - query is not supported.",
261+
clientType, endpoint.toString()));
262+
}
263+
if (StringUtils.isNotBlank(endpoint.getFragment())) {
264+
throw new ConfigurationError(
265+
String.format("quarkus.dynamodb.%s-client.proxy.endpoint (%s) - fragment is not supported.",
266+
clientType, endpoint.toString()));
267+
}
268+
}
269+
270+
private static void validateTlsManagersProvider(TlsManagersProviderConfig config, String clientType) {
271+
if (config != null && config.type.isPresent()
272+
&& config.type.get() == TlsManagersProviderType.FILE_STORE) {
273+
274+
if (config.fileStore == null) {
275+
throw new ConfigurationError(
276+
String.format(
277+
"quarkus.dynamodb.%s-client.tls-managers-provider.file-store must be specified if 'FILE_STORE' provider type is used",
278+
clientType));
279+
}
280+
if (config.fileStore.path == null) {
281+
throw new ConfigurationError(
282+
String.format(
283+
"quarkus.dynamodb.%s-client.tls-managers-provider.file-store.path should not be empty if 'FILE_STORE' provider is used.",
284+
clientType));
285+
}
286+
if (StringUtils.isBlank(config.fileStore.type)) {
287+
throw new ConfigurationError(
288+
String.format(
289+
"quarkus.dynamodb.%s-client.tls-managers-provider.file-store.type should not be empty if 'FILE_STORE' provider is used.",
290+
clientType));
291+
}
292+
if (StringUtils.isBlank(config.fileStore.password)) {
293+
throw new ConfigurationError(
294+
String.format(
295+
"quarkus.dynamodb.%s-client.tls-managers-provider.file-store.password should not be empty if 'FILE_STORE' provider is used.",
296+
clientType));
297+
}
298+
}
214299
}
215300
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package io.quarkus.dynamodb.deployment;
2+
3+
import javax.inject.Inject;
4+
5+
import org.jboss.shrinkwrap.api.ShrinkWrap;
6+
import org.jboss.shrinkwrap.api.spec.JavaArchive;
7+
import org.junit.jupiter.api.Assertions;
8+
import org.junit.jupiter.api.Test;
9+
import org.junit.jupiter.api.extension.RegisterExtension;
10+
11+
import io.quarkus.deployment.configuration.ConfigurationError;
12+
import io.quarkus.test.QuarkusUnitTest;
13+
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
14+
15+
public class DynamodbAsyncClientBrokenProxyConfigTest {
16+
17+
@Inject
18+
DynamoDbAsyncClient client;
19+
20+
@RegisterExtension
21+
static final QuarkusUnitTest config = new QuarkusUnitTest()
22+
.setExpectedException(ConfigurationError.class)
23+
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
24+
.addAsResource("async-broken-proxy-config.properties", "application.properties"));
25+
26+
@Test
27+
public void test() {
28+
// should not be called, deployment exception should happen first.
29+
Assertions.fail();
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package io.quarkus.dynamodb.deployment;
2+
3+
import javax.inject.Inject;
4+
5+
import org.jboss.shrinkwrap.api.ShrinkWrap;
6+
import org.jboss.shrinkwrap.api.spec.JavaArchive;
7+
import org.junit.jupiter.api.Test;
8+
import org.junit.jupiter.api.extension.RegisterExtension;
9+
10+
import io.quarkus.test.QuarkusUnitTest;
11+
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
12+
13+
public class DynamodbAsyncClientTlsFileStoreConfigTest {
14+
15+
@Inject
16+
DynamoDbAsyncClient client;
17+
18+
@RegisterExtension
19+
static final QuarkusUnitTest config = new QuarkusUnitTest()
20+
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
21+
.addAsResource("async-tls-filestore-config.properties", "application.properties"));
22+
23+
@Test
24+
public void test() {
25+
// Application should start with full config.
26+
}
27+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
quarkus.dynamodb.async-client.proxy.enabled = true
2+
quarkus.dynamodb.async-client.proxy.endpoint = http://user:[email protected]?foo=bar
3+
4+
5+

extensions/amazon-dynamodb/deployment/src/test/resources/async-full-config.properties

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
quarkus.dynamodb.region=us-east-1
21
quarkus.dynamodb.enable-endpoint-discovery=false
32
quarkus.dynamodb.endpoint-override=http://localhost:8000
43

5-
quarkus.dynamodb.credentials.type=STATIC
6-
quarkus.dynamodb.credentials.static-provider.access-key-id=test-key
7-
quarkus.dynamodb.credentials.static-provider.secret-access-key=test-secret
4+
quarkus.dynamodb.aws.region=us-east-1
5+
quarkus.dynamodb.aws.credentials.type=STATIC
6+
quarkus.dynamodb.aws.credentials.static-provider.access-key-id=test-key
7+
quarkus.dynamodb.aws.credentials.static-provider.secret-access-key=test-secret
88

99
quarkus.dynamodb.async-client.max-concurrency=10
1010
quarkus.dynamodb.async-client.max-pending-connection-acquires=10
@@ -21,3 +21,7 @@ quarkus.dynamodb.async-client.ssl-provider = JDK
2121
quarkus.dynamodb.async-client.event-loop.override = true
2222
quarkus.dynamodb.async-client.event-loop.number-of-threads = 5
2323
quarkus.dynamodb.async-client.event-loop.thread-name-prefix = Quarkus-Netty-EventLoop-
24+
quarkus.dynamodb.async-client.proxy.enabled = true
25+
quarkus.dynamodb.async-client.proxy.endpoint = http://127.1.1.1
26+
quarkus.dynamodb.async-client.proxy.non-proxy-hosts = localhost, hostlocal
27+
quarkus.dynamodb.async-client.tls-managers-provider.type=SYSTEM_PROPERTY
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
quarkus.dynamodb.aws.region=us-east-1
2+
3+
quarkus.dynamodb.async-client.tls-managers-provider.type=FILE_STORE
4+
quarkus.dynamodb.async-client.tls-managers-provider.file-store.path=/tmp/file.key
5+
quarkus.dynamodb.async-client.tls-managers-provider.file-store.type=pkcs11
6+
quarkus.dynamodb.async-client.tls-managers-provider.file-store.password=thePassword
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
1+
quarkus.dynamodb.aws.region=us-east-1
2+
13
quarkus.dynamodb.endpoint-override=127.0.0.1
24

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
quarkus.dynamodb.region=us-east-2
1+
quarkus.dynamodb.aws.region=us-east-2
22

3-
quarkus.dynamodb.credentials.type=DEFAULT
4-
quarkus.dynamodb.credentials.default-provider.async-credential-update-enabled=true
5-
quarkus.dynamodb.credentials.default-provider.reuse-last-provider-enabled=true
3+
quarkus.dynamodb.aws.credentials.type=DEFAULT
4+
quarkus.dynamodb.aws.credentials.default-provider.async-credential-update-enabled=true
5+
quarkus.dynamodb.aws.credentials.default-provider.reuse-last-provider-enabled=true
66

0 commit comments

Comments
 (0)