diff --git a/pom.xml b/pom.xml
index fbd80a720..5096a6f78 100644
--- a/pom.xml
+++ b/pom.xml
@@ -27,6 +27,7 @@
3.1.8-SNAPSHOT
3.2.9-SNAPSHOT
1.17.6
+ 5.15.0
@@ -156,11 +157,21 @@
consul
${testcontainers.version}
+
+ org.testcontainers
+ mockserver
+ ${testcontainers.version}
+
org.testcontainers
junit-jupiter
${testcontainers.version}
+
+ org.mock-server
+ mockserver-client-java
+ ${mockserverclient.version}
+
diff --git a/spring-cloud-consul-discovery/pom.xml b/spring-cloud-consul-discovery/pom.xml
index 41e1b7ad3..839a6a55c 100644
--- a/spring-cloud-consul-discovery/pom.xml
+++ b/spring-cloud-consul-discovery/pom.xml
@@ -134,6 +134,16 @@
consul
test
+
+ org.testcontainers
+ mockserver
+ test
+
+
+ org.mock-server
+ mockserver-client-java
+ test
+
org.testcontainers
junit-jupiter
diff --git a/spring-cloud-consul-discovery/src/main/java/org/springframework/cloud/consul/discovery/configclient/ConsulConfigServerBootstrapper.java b/spring-cloud-consul-discovery/src/main/java/org/springframework/cloud/consul/discovery/configclient/ConsulConfigServerBootstrapper.java
index 30a35d5d8..22fcd4fa2 100644
--- a/spring-cloud-consul-discovery/src/main/java/org/springframework/cloud/consul/discovery/configclient/ConsulConfigServerBootstrapper.java
+++ b/spring-cloud-consul-discovery/src/main/java/org/springframework/cloud/consul/discovery/configclient/ConsulConfigServerBootstrapper.java
@@ -17,14 +17,18 @@
package org.springframework.cloud.consul.discovery.configclient;
import java.util.Collections;
+import java.util.List;
import com.ecwid.consul.v1.ConsulClient;
+import org.apache.commons.logging.Log;
+import org.springframework.boot.BootstrapContext;
import org.springframework.boot.BootstrapRegistry;
import org.springframework.boot.BootstrapRegistryInitializer;
import org.springframework.boot.context.properties.bind.BindHandler;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
+import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.commons.util.InetUtils;
import org.springframework.cloud.commons.util.InetUtilsProperties;
import org.springframework.cloud.config.client.ConfigClientProperties;
@@ -84,24 +88,50 @@ public void initialize(BootstrapRegistry registry) {
discoveryClient);
}
});
- registry.registerIfAbsent(ConfigServerInstanceProvider.Function.class, context -> {
- if (!isDiscoveryEnabled(context.get(Binder.class))) {
- return (id) -> Collections.emptyList();
- }
- ConsulDiscoveryClient discoveryClient = context.get(ConsulDiscoveryClient.class);
- return discoveryClient::getInstances;
- });
-
+ registry.registerIfAbsent(ConfigServerInstanceProvider.Function.class, ConsulFunction::new);
}
private BindHandler getBindHandler(org.springframework.boot.BootstrapContext context) {
return context.getOrElse(BindHandler.class, null);
}
- private boolean isDiscoveryEnabled(Binder binder) {
+ private static boolean isDiscoveryEnabled(Binder binder) {
return binder.bind(ConfigClientProperties.CONFIG_DISCOVERY_ENABLED, Boolean.class).orElse(false)
&& binder.bind(ConditionalOnConsulDiscoveryEnabled.PROPERTY, Boolean.class).orElse(true)
&& binder.bind("spring.cloud.discovery.enabled", Boolean.class).orElse(true);
}
+ static final class ConsulFunction implements ConfigServerInstanceProvider.Function {
+
+ private final BootstrapContext context;
+
+ private ConsulFunction(BootstrapContext context) {
+ this.context = context;
+ }
+
+ @Override
+ public List apply(String serviceId) {
+ return apply(serviceId, null, null, null);
+ }
+
+ @Override
+ public List apply(String serviceId, Binder binder, BindHandler bindHandler, Log log) {
+ if (binder == null || !isDiscoveryEnabled(binder)) {
+ return Collections.emptyList();
+ }
+
+ ConsulProperties consulProperties = binder
+ .bind(ConsulProperties.PREFIX, Bindable.of(ConsulProperties.class), bindHandler)
+ .orElseGet(ConsulProperties::new);
+ ConsulClient consulClient = ConsulAutoConfiguration.createConsulClient(consulProperties);
+ ConsulDiscoveryProperties properties = binder
+ .bind(ConsulDiscoveryProperties.PREFIX, Bindable.of(ConsulDiscoveryProperties.class), bindHandler)
+ .orElseGet(() -> new ConsulDiscoveryProperties(new InetUtils(new InetUtilsProperties())));
+ ConsulDiscoveryClient discoveryClient = new ConsulDiscoveryClient(consulClient, properties);
+
+ return discoveryClient.getInstances(serviceId);
+ }
+
+ }
+
}
diff --git a/spring-cloud-consul-discovery/src/test/java/org/springframework/cloud/consul/discovery/configclient/ConsulConfigServerBootstrapperIT.java b/spring-cloud-consul-discovery/src/test/java/org/springframework/cloud/consul/discovery/configclient/ConsulConfigServerBootstrapperIT.java
new file mode 100644
index 000000000..ff0a38144
--- /dev/null
+++ b/spring-cloud-consul-discovery/src/test/java/org/springframework/cloud/consul/discovery/configclient/ConsulConfigServerBootstrapperIT.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2012-2023 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.consul.discovery.configclient;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import com.ecwid.consul.v1.ConsulClient;
+import com.ecwid.consul.v1.agent.model.NewService;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockserver.client.MockServerClient;
+import org.testcontainers.consul.ConsulContainer;
+import org.testcontainers.containers.MockServerContainer;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.DockerImageName;
+
+import org.springframework.boot.SpringBootConfiguration;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.cloud.config.environment.Environment;
+import org.springframework.cloud.config.environment.PropertySource;
+import org.springframework.cloud.consul.ConsulAutoConfiguration;
+import org.springframework.cloud.consul.ConsulProperties;
+import org.springframework.cloud.consul.test.ConsulTestcontainers;
+import org.springframework.context.ConfigurableApplicationContext;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockserver.model.HttpRequest.request;
+import static org.mockserver.model.HttpResponse.response;
+
+/**
+ * @author Ryan Baxter
+ */
+
+@Testcontainers
+public class ConsulConfigServerBootstrapperIT {
+
+ public static final DockerImageName MOCKSERVER_IMAGE = DockerImageName.parse("mockserver/mockserver")
+ .withTag("mockserver-" + MockServerClient.class.getPackage().getImplementationVersion());
+
+ @Container
+ static ConsulContainer consul = ConsulTestcontainers.createConsulContainer("1.10");
+
+ @Container
+ static MockServerContainer mockServer = new MockServerContainer(MOCKSERVER_IMAGE);
+
+ private ConfigurableApplicationContext context;
+
+ @BeforeEach
+ void before() {
+ ConsulProperties consulProperties = new ConsulProperties();
+ consulProperties.setHost(consul.getHost());
+ consulProperties.setPort(consul.getMappedPort(ConsulTestcontainers.DEFAULT_PORT));
+ ConsulClient client = ConsulAutoConfiguration.createConsulClient(consulProperties);
+ NewService newService = new NewService();
+ newService.setId("consul-configserver");
+ newService.setName("consul-configserver");
+ newService.setAddress(mockServer.getHost());
+ newService.setPort(mockServer.getServerPort());
+ client.agentServiceRegister(newService);
+
+ }
+
+ @AfterEach
+ void after() {
+ this.context.close();
+ }
+
+ @Test
+ public void contextLoads() throws JsonProcessingException {
+ Environment environment = new Environment("test", "default");
+ Map properties = new HashMap<>();
+ properties.put("hello", "world");
+ PropertySource p = new PropertySource("p1", properties);
+ environment.add(p);
+ ObjectMapper objectMapper = new ObjectMapper();
+ try (MockServerClient mockServerClient = new MockServerClient(mockServer.getHost(),
+ mockServer.getMappedPort(MockServerContainer.PORT))) {
+ mockServerClient.when(request().withPath("/application/default"))
+ .respond(response().withBody(objectMapper.writeValueAsString(environment))
+ .withHeader("content-type", "application/json"));
+ this.context = setup().run();
+ assertThat(this.context.getEnvironment().getProperty("hello")).isEqualTo("world");
+ }
+
+ }
+
+ SpringApplicationBuilder setup(String... env) {
+ SpringApplicationBuilder builder = new SpringApplicationBuilder(TestConfig.class)
+ .properties(addDefaultEnv(env));
+ return builder;
+ }
+
+ private String[] addDefaultEnv(String[] env) {
+ Set set = new LinkedHashSet<>();
+ if (env != null && env.length > 0) {
+ set.addAll(Arrays.asList(env));
+ }
+ set.add("spring.config.import=classpath:bootstrapper.yaml");
+ set.add("spring.cloud.config.enabled=true");
+ set.add("spring.cloud.service-registry.auto-registration.enabled=false");
+ set.add(ConsulProperties.PREFIX + ".host=" + consul.getHost());
+ set.add(ConsulProperties.PREFIX + ".port=" + consul.getMappedPort(ConsulTestcontainers.DEFAULT_PORT));
+ return set.toArray(new String[0]);
+ }
+
+ @SpringBootConfiguration
+ @EnableAutoConfiguration
+ static class TestConfig {
+
+ }
+
+}
diff --git a/spring-cloud-consul-discovery/src/test/java/org/springframework/cloud/consul/discovery/configclient/ConsulConfigServerBootstrapperTests.java b/spring-cloud-consul-discovery/src/test/java/org/springframework/cloud/consul/discovery/configclient/ConsulConfigServerBootstrapperTests.java
index dc1d61e91..1645de90b 100644
--- a/spring-cloud-consul-discovery/src/test/java/org/springframework/cloud/consul/discovery/configclient/ConsulConfigServerBootstrapperTests.java
+++ b/spring-cloud-consul-discovery/src/test/java/org/springframework/cloud/consul/discovery/configclient/ConsulConfigServerBootstrapperTests.java
@@ -20,6 +20,7 @@
import java.util.concurrent.atomic.AtomicReference;
import com.ecwid.consul.transport.TransportException;
+import org.apache.commons.logging.Log;
import org.junit.jupiter.api.Test;
import org.springframework.boot.BootstrapRegistry;
@@ -29,14 +30,27 @@
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.properties.bind.BindContext;
import org.springframework.boot.context.properties.bind.BindHandler;
+import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.boot.context.properties.bind.Bindable;
+import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
+import org.springframework.cloud.commons.util.InetUtils;
+import org.springframework.cloud.commons.util.InetUtilsProperties;
+import org.springframework.cloud.config.client.ConfigClientProperties;
import org.springframework.cloud.config.client.ConfigServerInstanceProvider;
+import org.springframework.cloud.consul.ConsulProperties;
+import org.springframework.cloud.consul.discovery.ConditionalOnConsulDiscoveryEnabled;
import org.springframework.cloud.consul.discovery.ConsulDiscoveryClient;
+import org.springframework.cloud.consul.discovery.ConsulDiscoveryProperties;
import org.springframework.context.ConfigurableApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
public class ConsulConfigServerBootstrapperTests {
@@ -47,7 +61,14 @@ public void notEnabledDoesNotAddInstanceProviderFn() {
.addBootstrapRegistryInitializer(registry -> registry.addCloseListener(event -> {
ConfigServerInstanceProvider.Function providerFn = event.getBootstrapContext()
.get(ConfigServerInstanceProvider.Function.class);
- assertThat(providerFn.apply("id"))
+ BindResult falseBindResult = mock(BindResult.class);
+ when(falseBindResult.orElse(anyBoolean())).thenReturn(false);
+ Binder binder = mock(Binder.class);
+ when(binder.bind(eq(ConfigClientProperties.CONFIG_DISCOVERY_ENABLED), eq(Boolean.class)))
+ .thenReturn(falseBindResult);
+ BindHandler bindHandler = mock(BindHandler.class);
+ Log log = mock(Log.class);
+ assertThat(providerFn.apply("id", binder, bindHandler, log))
.as("ConfigServerInstanceProvider.Function should return empty list")
.isEqualTo(Collections.EMPTY_LIST);
})).run().close();
@@ -56,12 +77,22 @@ public void notEnabledDoesNotAddInstanceProviderFn() {
@Test
public void consulDiscoveryClientNotEnabledProvidesEmptyList() {
new SpringApplicationBuilder(TestConfig.class)
- .properties("--server.port=0", "spring.cloud.service-registry.auto-registration.enabled=false",
- "spring.cloud.config.discovery.enabled=true", "spring.cloud.consul.discovery.enabled=false")
+ .properties("--server.port=0", "spring.cloud.service-registry.auto-registration.enabled=false")
.addBootstrapRegistryInitializer(registry -> registry.addCloseListener(event -> {
ConfigServerInstanceProvider.Function providerFn = event.getBootstrapContext()
.get(ConfigServerInstanceProvider.Function.class);
- assertThat(providerFn.apply("id"))
+ BindResult falseBindResult = mock(BindResult.class);
+ when(falseBindResult.orElse(anyBoolean())).thenReturn(false);
+ BindResult trueBindResult = mock(BindResult.class);
+ when(trueBindResult.orElse(anyBoolean())).thenReturn(true);
+ Binder binder = mock(Binder.class);
+ when(binder.bind(eq(ConfigClientProperties.CONFIG_DISCOVERY_ENABLED), eq(Boolean.class)))
+ .thenReturn(trueBindResult);
+ when(binder.bind(eq(ConditionalOnConsulDiscoveryEnabled.PROPERTY), eq(Boolean.class)))
+ .thenReturn(falseBindResult);
+ BindHandler bindHandler = mock(BindHandler.class);
+ Log log = mock(Log.class);
+ assertThat(providerFn.apply("id", binder, bindHandler, log))
.as("ConfigServerInstanceProvider.Function should return empty list")
.isEqualTo(Collections.EMPTY_LIST);
})).run().close();
@@ -70,12 +101,24 @@ public void consulDiscoveryClientNotEnabledProvidesEmptyList() {
@Test
public void springCloudDiscoveryClientNotEnabledProvidesEmptyList() {
new SpringApplicationBuilder(TestConfig.class)
- .properties("--server.port=0", "spring.cloud.service-registry.auto-registration.enabled=false",
- "spring.cloud.config.discovery.enabled=true", "spring.cloud.discovery.enabled=false")
+ .properties("--server.port=0", "spring.cloud.service-registry.auto-registration.enabled=false")
.addBootstrapRegistryInitializer(registry -> registry.addCloseListener(event -> {
ConfigServerInstanceProvider.Function providerFn = event.getBootstrapContext()
.get(ConfigServerInstanceProvider.Function.class);
- assertThat(providerFn.apply("id"))
+ BindResult falseBindResult = mock(BindResult.class);
+ when(falseBindResult.orElse(anyBoolean())).thenReturn(false);
+ BindResult trueBindResult = mock(BindResult.class);
+ when(trueBindResult.orElse(anyBoolean())).thenReturn(true);
+ Binder binder = mock(Binder.class);
+ when(binder.bind(eq(ConfigClientProperties.CONFIG_DISCOVERY_ENABLED), eq(Boolean.class)))
+ .thenReturn(trueBindResult);
+ when(binder.bind(eq(ConditionalOnConsulDiscoveryEnabled.PROPERTY), eq(Boolean.class)))
+ .thenReturn(trueBindResult);
+ when(binder.bind(eq("spring.cloud.discovery.enabled"), eq(Boolean.class)))
+ .thenReturn(falseBindResult);
+ BindHandler bindHandler = mock(BindHandler.class);
+ Log log = mock(Log.class);
+ assertThat(providerFn.apply("id", binder, bindHandler, log))
.as("ConfigServerInstanceProvider.Function should return empty list")
.isEqualTo(Collections.EMPTY_LIST);
})).run().close();
@@ -88,14 +131,45 @@ public void enabledAddsInstanceProviderFn() {
ConfigurableApplicationContext context = new SpringApplicationBuilder(TestConfig.class)
.properties("--server.port=0", "spring.cloud.config.discovery.enabled=true",
"spring.cloud.consul.discovery.hostname=myhost",
- "spring.cloud.service-registry.auto-registration.enabled=false",
- "spring.cloud.consul.host=localhost")
+ "spring.cloud.service-registry.auto-registration.enabled=false")
.addBootstrapRegistryInitializer(bindHandlerBootstrapper)
.addBootstrapRegistryInitializer(registry -> registry.addCloseListener(event -> {
bootstrapDiscoveryClient.set(event.getBootstrapContext().get(ConsulDiscoveryClient.class));
ConfigServerInstanceProvider.Function providerFn = event.getBootstrapContext()
.get(ConfigServerInstanceProvider.Function.class);
- assertThatThrownBy(() -> providerFn.apply("id")).isInstanceOf(TransportException.class)
+
+ BindHandler bindHandler = mock(BindHandler.class);
+ Binder binder = mock(Binder.class);
+ Log log = mock(Log.class);
+
+ BindResult falseBindResult = mock(BindResult.class);
+ when(falseBindResult.orElse(anyBoolean())).thenReturn(false);
+ BindResult trueBindResult = mock(BindResult.class);
+ when(trueBindResult.orElse(anyBoolean())).thenReturn(true);
+
+ when(binder.bind(eq(ConfigClientProperties.CONFIG_DISCOVERY_ENABLED), eq(Boolean.class)))
+ .thenReturn(trueBindResult);
+ when(binder.bind(eq(ConditionalOnConsulDiscoveryEnabled.PROPERTY), eq(Boolean.class)))
+ .thenReturn(trueBindResult);
+ when(binder.bind(eq("spring.cloud.discovery.enabled"), eq(Boolean.class)))
+ .thenReturn(trueBindResult);
+
+ BindResult consulPropertiesResult = mock(BindResult.class);
+ when(consulPropertiesResult.orElseGet(any())).thenReturn(new ConsulProperties());
+ when(binder.bind(ConsulProperties.PREFIX, Bindable.of(ConsulProperties.class), bindHandler))
+ .thenReturn(consulPropertiesResult);
+
+ BindResult consulDiscoveryPropertiesResult = mock(BindResult.class);
+ ConsulDiscoveryProperties discoveryProperties = new ConsulDiscoveryProperties(
+ new InetUtils(new InetUtilsProperties()));
+ discoveryProperties.setHostname("myhost");
+ when(consulDiscoveryPropertiesResult.orElseGet(any())).thenReturn(discoveryProperties);
+
+ when(binder.bind(ConsulDiscoveryProperties.PREFIX, Bindable.of(ConsulDiscoveryProperties.class),
+ bindHandler)).thenReturn(consulDiscoveryPropertiesResult);
+
+ assertThatThrownBy(() -> providerFn.apply("id", binder, bindHandler, log))
+ .isInstanceOf(TransportException.class)
.hasMessageContaining(
"org.apache.http.conn.HttpHostConnectException: Connect to localhost:8500")
.as("Should have tried to reach out to Consul to get config server instance").isNotNull();
diff --git a/spring-cloud-consul-discovery/src/test/resources/bootstrapper.yaml b/spring-cloud-consul-discovery/src/test/resources/bootstrapper.yaml
new file mode 100644
index 000000000..13b4e728e
--- /dev/null
+++ b/spring-cloud-consul-discovery/src/test/resources/bootstrapper.yaml
@@ -0,0 +1,8 @@
+spring:
+ config:
+ import: "optional:configserver:"
+ cloud:
+ config:
+ discovery:
+ service-id: consul-configserver
+ enabled: true
diff --git a/spring-cloud-consul-discovery/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/spring-cloud-consul-discovery/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 000000000..ca6ee9cea
--- /dev/null
+++ b/spring-cloud-consul-discovery/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
\ No newline at end of file