Add support for ReactiveDiscoveryClient

ReactiveDiscoveryClient beans are now used to configure ReactiveVaultEndpointProvider. If no ReactiveDiscoveryClient is available, then VaultEndpointProvider gets adapted by offloading the call to a worker Scheduler to avoid blocking calls on the calling thread.

Resolves gh-486.
This commit is contained in:
Mark Paluch
2020-09-24 12:35:19 +02:00
parent 17aa58d66e
commit 7c231daddb
11 changed files with 497 additions and 28 deletions

View File

@@ -8,3 +8,4 @@ This section briefly covers items that are new and noteworthy in the latest rele
* Migration of `PropertySource` initialization from Spring Cloud's Bootstrap Context to Spring Boot's <<vault.configdata,ConfigData API>>.
* Support for the <<vault.config.backends.couchbase>> backend.
* Configuration of keystore/truststore types through `spring.cloud.vault.ssl.key-store-type=…`/`spring.cloud.vault.ssl.trust-store-type=…` including PEM support.
* Support for `ReactiveDiscoveryClient` by configuring a `ReactiveVaultEndpointProvider`.

View File

@@ -16,8 +16,6 @@
package org.springframework.cloud.vault.config;
import java.net.URI;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -30,8 +28,6 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.util.StringUtils;
import org.springframework.vault.client.VaultEndpoint;
import org.springframework.vault.client.VaultEndpointProvider;
/**
@@ -51,8 +47,11 @@ public class DiscoveryClientVaultBootstrapConfiguration {
private final VaultProperties vaultProperties;
private final VaultConfiguration configuration;
public DiscoveryClientVaultBootstrapConfiguration(VaultProperties vaultProperties) {
this.vaultProperties = vaultProperties;
this.configuration = new VaultConfiguration(vaultProperties);
}
@Bean
@@ -67,28 +66,13 @@ public class DiscoveryClientVaultBootstrapConfiguration {
@ConditionalOnProperty(name = "spring.cloud.vault.enabled", matchIfMissing = true)
public VaultEndpointProvider vaultEndpointProvider(VaultServiceInstanceProvider instanceProvider) {
String serviceId = this.vaultProperties.getDiscovery().getServiceId();
String fallbackScheme;
return () -> {
if (StringUtils.hasText(this.vaultProperties.getUri())) {
fallbackScheme = URI.create(this.vaultProperties.getUri()).getScheme();
}
else {
fallbackScheme = this.vaultProperties.getScheme();
}
String serviceId = this.vaultProperties.getDiscovery().getServiceId();
ServiceInstance server = instanceProvider.getVaultServerInstance(serviceId);
ServiceInstance server = instanceProvider.getVaultServerInstance(serviceId);
VaultEndpoint vaultEndpoint = VaultEndpoint.create(server.getHost(), server.getPort());
if (server.getMetadata().containsKey("scheme")) {
vaultEndpoint.setScheme(server.getMetadata().get("scheme"));
}
else {
vaultEndpoint.setScheme(server.isSecure() ? "https" : fallbackScheme);
}
return () -> vaultEndpoint;
return this.configuration.createVaultEndpoint(server);
};
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright 2017-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.vault.config;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
import org.springframework.cloud.commons.util.UtilAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.vault.client.ReactiveVaultEndpointProvider;
import org.springframework.vault.client.VaultEndpointProvider;
import org.springframework.vault.core.ReactiveVaultOperations;
import org.springframework.web.reactive.function.client.WebClient;
/**
* {@link org.springframework.cloud.bootstrap.BootstrapConfiguration} providing a
* {@link VaultEndpointProvider} using {@link DiscoveryClient}.
*
* @author Mark Paluch
* @since 1.1
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty("spring.cloud.vault.discovery.enabled")
@ConditionalOnExpression("${spring.cloud.vault.reactive.enabled:true}")
@ConditionalOnClass({ Flux.class, WebClient.class, ReactiveVaultOperations.class, ReactiveDiscoveryClient.class })
@EnableConfigurationProperties(VaultProperties.class)
@Order(Ordered.LOWEST_PRECEDENCE - 5)
@Import(UtilAutoConfiguration.class)
public class ReactiveDiscoveryClientVaultBootstrapConfiguration {
private final VaultProperties vaultProperties;
private final VaultConfiguration configuration;
public ReactiveDiscoveryClientVaultBootstrapConfiguration(VaultProperties vaultProperties) {
this.vaultProperties = vaultProperties;
this.configuration = new VaultConfiguration(vaultProperties);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "spring.cloud.vault.enabled", matchIfMissing = true)
public ReactiveVaultEndpointProvider reactiveVaultEndpointProvider(
ObjectProvider<ReactiveDiscoveryClient> reactiveDiscoveryClients,
ObjectProvider<VaultEndpointProvider> endpointProviders) {
ReactiveDiscoveryClient reactiveDiscoveryClient = reactiveDiscoveryClients.getIfAvailable();
if (reactiveDiscoveryClient != null) {
ReacvtiveDiscoveryClientVaultServiceInstanceProvider instanceProvider = new ReacvtiveDiscoveryClientVaultServiceInstanceProvider(
reactiveDiscoveryClient);
return () -> Mono.defer(() -> {
String serviceId = this.vaultProperties.getDiscovery().getServiceId();
return instanceProvider.getVaultServerInstance(serviceId).map(this.configuration::createVaultEndpoint);
});
}
VaultEndpointProvider endpointProvider = endpointProviders.getObject();
return () -> Mono.fromSupplier(endpointProvider::getVaultEndpoint).subscribeOn(Schedulers.boundedElastic());
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2018-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.vault.config;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Mono;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
/**
* Provider for {@link ServiceInstance} to look up the Vault service.
*
* @author Mark Paluch
* @since 3.0
*/
class ReacvtiveDiscoveryClientVaultServiceInstanceProvider {
private static final Log log = LogFactory.getLog(ReacvtiveDiscoveryClientVaultServiceInstanceProvider.class);
private final ReactiveDiscoveryClient client;
ReacvtiveDiscoveryClientVaultServiceInstanceProvider(ReactiveDiscoveryClient client) {
this.client = client;
}
Mono<ServiceInstance> getVaultServerInstance(String serviceId) {
log.debug("Locating Vault server (" + serviceId + ") via discovery");
return this.client.getInstances(serviceId).collectList().handle((instances, sink) -> {
if (instances.isEmpty()) {
sink.error(new IllegalStateException("No instances found of Vault server (" + serviceId + ")"));
return;
}
ServiceInstance instance = instances.get(0);
log.debug("Located Vault server (" + serviceId + ") via discovery: " + instance);
sink.next(instance);
});
}
}

View File

@@ -21,6 +21,7 @@ import java.time.Duration;
import java.util.List;
import java.util.function.Supplier;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.vault.config.VaultProperties.Ssl;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.scheduling.TaskScheduler;
@@ -128,6 +129,27 @@ final class VaultConfiguration {
return vaultEndpoint;
}
VaultEndpoint createVaultEndpoint(ServiceInstance server) {
String fallbackScheme;
if (StringUtils.hasText(this.vaultProperties.getUri())) {
fallbackScheme = URI.create(this.vaultProperties.getUri()).getScheme();
}
else {
fallbackScheme = this.vaultProperties.getScheme();
}
VaultEndpoint vaultEndpoint = VaultEndpoint.create(server.getHost(), server.getPort());
if (server.getMetadata().containsKey("scheme")) {
vaultEndpoint.setScheme(server.getMetadata().get("scheme"));
}
else {
vaultEndpoint.setScheme(server.isSecure() ? "https" : fallbackScheme);
}
return vaultEndpoint;
}
RestTemplateBuilder createRestTemplateBuilder(ClientHttpRequestFactory requestFactory,
VaultEndpointProvider endpointProvider, List<RestTemplateCustomizer> customizers,
List<RestTemplateRequestCustomizer<?>> requestCustomizers) {

View File

@@ -47,6 +47,7 @@ import org.springframework.vault.authentication.ReactiveLifecycleAwareSessionMan
import org.springframework.vault.authentication.ReactiveSessionManager;
import org.springframework.vault.authentication.SessionManager;
import org.springframework.vault.authentication.VaultTokenSupplier;
import org.springframework.vault.client.ReactiveVaultEndpointProvider;
import org.springframework.vault.client.SimpleVaultEndpointProvider;
import org.springframework.vault.client.VaultEndpointProvider;
import org.springframework.vault.client.WebClientBuilder;
@@ -82,6 +83,10 @@ public class VaultReactiveAutoConfiguration implements InitializingBean {
private final VaultReactiveConfiguration configuration;
@Nullable
private final ReactiveVaultEndpointProvider reactiveEndpointProvider;
@Nullable
private final VaultEndpointProvider endpointProvider;
private final List<WebClientCustomizer> customizers;
@@ -96,14 +101,23 @@ public class VaultReactiveAutoConfiguration implements InitializingBean {
private WebClientBuilder webClientBuilder;
public VaultReactiveAutoConfiguration(VaultProperties vaultProperties,
ObjectProvider<ReactiveVaultEndpointProvider> reactiveEndpointProvider,
ObjectProvider<VaultEndpointProvider> endpointProvider,
ObjectProvider<List<WebClientCustomizer>> webClientCustomizers) {
this.vaultProperties = vaultProperties;
this.configuration = new VaultReactiveConfiguration(vaultProperties);
this.endpointProvider = endpointProvider.getIfAvailable(
() -> SimpleVaultEndpointProvider.of(new VaultConfiguration(vaultProperties).createVaultEndpoint()));
this.reactiveEndpointProvider = reactiveEndpointProvider.getIfAvailable();
if (this.reactiveEndpointProvider == null) {
this.endpointProvider = endpointProvider.getIfAvailable(() -> SimpleVaultEndpointProvider
.of(new VaultConfiguration(vaultProperties).createVaultEndpoint()));
}
else {
this.endpointProvider = null;
}
this.customizers = new ArrayList<>(webClientCustomizers.getIfAvailable(Collections::emptyList));
AnnotationAwareOrderComparator.sort(this.customizers);
}
@@ -117,7 +131,18 @@ public class VaultReactiveAutoConfiguration implements InitializingBean {
}
protected WebClientBuilder webClientBuilder(ClientHttpConnector connector) {
return this.configuration.createWebClientBuilder(connector, this.endpointProvider, this.customizers);
if (this.reactiveEndpointProvider != null) {
return this.configuration.createWebClientBuilder(connector, this.reactiveEndpointProvider,
this.customizers);
}
if (this.endpointProvider != null) {
return this.configuration.createWebClientBuilder(connector, this.endpointProvider, this.customizers);
}
throw new IllegalStateException(
"Cannot create WebClientBuilder as neither ReactiveEndpointProvider nor EndpointProvider configured");
}
/**

View File

@@ -27,6 +27,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.bootstrap.BootstrapConfiguration;
import org.springframework.vault.client.ReactiveVaultEndpointProvider;
import org.springframework.vault.client.VaultEndpointProvider;
import org.springframework.vault.client.WebClientCustomizer;
import org.springframework.vault.core.ReactiveVaultOperations;
@@ -53,9 +54,10 @@ import org.springframework.web.reactive.function.client.WebClient;
public class VaultReactiveBootstrapConfiguration extends VaultReactiveAutoConfiguration {
public VaultReactiveBootstrapConfiguration(VaultProperties vaultProperties,
ObjectProvider<ReactiveVaultEndpointProvider> reactiveEndpointProvider,
ObjectProvider<VaultEndpointProvider> endpointProvider,
ObjectProvider<List<WebClientCustomizer>> webClientCustomizers) {
super(vaultProperties, endpointProvider, webClientCustomizers);
super(vaultProperties, reactiveEndpointProvider, endpointProvider, webClientCustomizers);
}
}

View File

@@ -36,6 +36,7 @@ import org.springframework.vault.authentication.SessionManager;
import org.springframework.vault.authentication.TokenAuthentication;
import org.springframework.vault.authentication.VaultTokenSupplier;
import org.springframework.vault.client.ClientHttpConnectorFactory;
import org.springframework.vault.client.ReactiveVaultEndpointProvider;
import org.springframework.vault.client.VaultEndpointProvider;
import org.springframework.vault.client.VaultHttpHeaders;
import org.springframework.vault.client.WebClientBuilder;
@@ -70,12 +71,25 @@ final class VaultReactiveConfiguration {
return ClientHttpConnectorFactory.create(clientOptions, sslConfiguration);
}
WebClientBuilder createWebClientBuilder(ClientHttpConnector connector,
ReactiveVaultEndpointProvider endpointProvider, List<WebClientCustomizer> customizers) {
WebClientBuilder builder = WebClientBuilder.builder().httpConnector(connector)
.endpointProvider(endpointProvider);
return applyCustomizer(customizers, builder);
}
WebClientBuilder createWebClientBuilder(ClientHttpConnector connector, VaultEndpointProvider endpointProvider,
List<WebClientCustomizer> customizers) {
WebClientBuilder builder = WebClientBuilder.builder().httpConnector(connector)
.endpointProvider(endpointProvider);
return applyCustomizer(customizers, builder);
}
private WebClientBuilder applyCustomizer(List<WebClientCustomizer> customizers, WebClientBuilder builder) {
customizers.forEach(builder::customizers);
if (StringUtils.hasText(this.vaultProperties.getNamespace())) {

View File

@@ -6,6 +6,7 @@ org.springframework.cloud.vault.config.VaultHealthIndicatorAutoConfiguration
# Bootstrap Configuration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.vault.config.DiscoveryClientVaultBootstrapConfiguration,\
org.springframework.cloud.vault.config.ReactiveDiscoveryClientVaultBootstrapConfiguration,\
org.springframework.cloud.vault.config.VaultBootstrapConfiguration,\
org.springframework.cloud.vault.config.VaultReactiveBootstrapConfiguration,\
org.springframework.cloud.vault.config.VaultBootstrapPropertySourceConfiguration

View File

@@ -0,0 +1,221 @@
/*
* Copyright 2018-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.vault.config;
import java.net.URI;
import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.Test;
import org.mockito.Mockito;
import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.vault.client.ReactiveVaultEndpointProvider;
import org.springframework.vault.client.VaultEndpoint;
import org.springframework.vault.client.VaultEndpointProvider;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
/**
* Tests for {@link ReactiveDiscoveryClientVaultBootstrapConfiguration}.
*
* @author Mark Paluch
*/
public class ReactiveDiscoveryClientVaultBootstrapConfigurationTests {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration(AutoConfigurations
.of(ReactiveDiscoveryClientVaultBootstrapConfiguration.class, VaultBootstrapConfiguration.class));
@Test
public void shouldRegisterDefaultBeans() {
this.contextRunner.withUserConfiguration(ReactiveDiscoveryConfiguration.class)
.withPropertyValues("spring.cloud.vault.token=foo", "spring.cloud.vault.discovery.enabled=true",
"spring.cloud.bootstrap.enabled=true")
.run(context -> {
assertThat(context).hasSingleBean(ReactiveVaultEndpointProvider.class);
ReactiveVaultEndpointProvider endpointProvider = context
.getBean(ReactiveVaultEndpointProvider.class);
endpointProvider.getVaultEndpoint().as(StepVerifier::create).assertNext(actual -> {
assertThat(actual.getPort()).isEqualTo(1234);
}).verifyComplete();
});
}
@Test
public void shouldRegisterVaultEndpointAdapterBean() {
this.contextRunner.withUserConfiguration(BridgedDiscoveryConfiguration.class)
.withPropertyValues("spring.cloud.vault.token=foo", "spring.cloud.vault.discovery.enabled=true",
"spring.cloud.bootstrap.enabled=true")
.run(context -> {
assertThat(context).hasSingleBean(ReactiveVaultEndpointProvider.class);
ReactiveVaultEndpointProvider endpointProvider = context
.getBean(ReactiveVaultEndpointProvider.class);
endpointProvider.getVaultEndpoint().as(StepVerifier::create).assertNext(actual -> {
assertThat(actual.getPort()).isEqualTo(1234);
}).verifyComplete();
});
}
@Test
public void shouldNotRegisterBeansIfDiscoveryDisabled() {
this.contextRunner.withUserConfiguration(ReactiveDiscoveryConfiguration.class)
.withPropertyValues("spring.cloud.vault.token=foo", "spring.cloud.vault.discovery.enabled=false",
"spring.cloud.bootstrap.enabled=true")
.run(context -> {
assertThat(context.getBeanNamesForType(ReactiveVaultEndpointProvider.class)).isEmpty();
});
}
@Test
public void shouldNotRegisterBeansIfVaultDisabled() {
this.contextRunner.withUserConfiguration(ReactiveDiscoveryConfiguration.class)
.withPropertyValues("spring.cloud.vault.token=foo", "spring.cloud.vault.enabled=false",
"spring.cloud.bootstrap.enabled=true")
.run(context -> {
assertThat(context.getBeanNamesForType(ReactiveVaultEndpointProvider.class)).isEmpty();
});
}
@Configuration(proxyBeanMethods = false)
static class ReactiveDiscoveryConfiguration {
@Bean
ReactiveDiscoveryClient reactiveDiscoveryClient() {
ReactiveDiscoveryClient mock = Mockito.mock(ReactiveDiscoveryClient.class);
when(mock.getInstances(anyString()))
.thenReturn(Flux.just(new SimpleServiceInstance(URI.create("https://foo:1234"))));
return mock;
}
}
@Configuration(proxyBeanMethods = false)
static class BridgedDiscoveryConfiguration {
@Bean
VaultEndpointProvider vaultEndpointProvider() {
VaultEndpointProvider mock = Mockito.mock(VaultEndpointProvider.class);
VaultEndpoint vaultEndpoint = VaultEndpoint.create("foo", 1234);
when(mock.getVaultEndpoint()).thenReturn(vaultEndpoint);
return mock;
}
}
static class SimpleServiceInstance implements ServiceInstance {
private URI uri;
private String host;
private int port;
private boolean secure;
private Map<String, String> metadata = new LinkedHashMap<>();
private String serviceId;
SimpleServiceInstance(URI uri) {
this.setUri(uri);
}
void setUri(URI uri) {
this.uri = uri;
this.host = this.uri.getHost();
this.port = this.uri.getPort();
String scheme = this.uri.getScheme();
if ("https".equals(scheme)) {
this.secure = true;
}
}
public URI getUri() {
return this.uri;
}
public String getHost() {
return this.host;
}
public int getPort() {
return this.port;
}
public boolean isSecure() {
return this.secure;
}
public Map<String, String> getMetadata() {
return this.metadata;
}
public String getServiceId() {
return this.serviceId;
}
public void setHost(String host) {
this.host = host;
}
public void setPort(int port) {
this.port = port;
}
public void setSecure(boolean secure) {
this.secure = secure;
}
public void setMetadata(Map<String, String> metadata) {
this.metadata = metadata;
}
public void setServiceId(String serviceId) {
this.serviceId = serviceId;
}
}
}

View File

@@ -21,6 +21,7 @@ import java.util.concurrent.atomic.AtomicLong;
import org.junit.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
@@ -37,6 +38,8 @@ import org.springframework.vault.authentication.ReactiveSessionManager;
import org.springframework.vault.authentication.SessionManager;
import org.springframework.vault.authentication.SimpleSessionManager;
import org.springframework.vault.authentication.VaultTokenSupplier;
import org.springframework.vault.client.ReactiveVaultEndpointProvider;
import org.springframework.vault.client.VaultEndpoint;
import org.springframework.vault.client.WebClientFactory;
import org.springframework.vault.core.ReactiveVaultOperations;
import org.springframework.vault.core.ReactiveVaultTemplate;
@@ -158,6 +161,29 @@ public class VaultReactiveAutoConfigurationTests {
});
}
@Test
public void shouldConfigureEndpointProvider() {
this.contextRunner
.withPropertyValues("spring.cloud.vault.kv.enabled=false", "spring.cloud.vault.token=foo",
"spring.cloud.vault.session.lifecycle.enabled=false")
.withUserConfiguration(ReactiveEndpointProviderConfiguration.class)
.withBean("vaultTokenSupplier", VaultTokenSupplier.class, () -> Mono::empty)
.withBean("taskSchedulerWrapper", VaultAutoConfiguration.TaskSchedulerWrapper.class,
() -> new VaultAutoConfiguration.TaskSchedulerWrapper(new ThreadPoolTaskScheduler()))
.run(context -> {
WebClientFactory factory = context.getBean(WebClientFactory.class);
WebClient webClient = factory.create();
webClient.get().uri("foo").retrieve().bodyToMono(String.class).as(StepVerifier::create)
.verifyErrorMatches(throwable -> throwable.getMessage().contains("foobar-1"));
webClient.get().uri("foo").retrieve().bodyToMono(String.class).as(StepVerifier::create)
.verifyErrorMatches(throwable -> throwable.getMessage().contains("foobar-2"));
});
}
@Configuration(proxyBeanMethods = false)
static class AuthenticationFactoryConfiguration {
@@ -189,4 +215,23 @@ public class VaultReactiveAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class ReactiveEndpointProviderConfiguration {
@Bean
ReactiveVaultEndpointProvider reactiveVaultEndpointProvider() {
AtomicLong counter = new AtomicLong();
return () -> Mono.fromSupplier(() -> {
VaultEndpoint vaultEndpoint = new VaultEndpoint();
vaultEndpoint.setHost("foobar-" + counter.incrementAndGet());
return vaultEndpoint;
});
}
}
}