Adds support for paths in ConfigData
Now `consul:myhost:8501/mypath1;/mypath2;/mypath3` will use the paths separated by semicolon (;) instead of automatically generating paths. `consul:myhost:8501` or `consul:` will automatically generate paths from ConsulConfigProperties and active profiles.
This commit is contained in:
@@ -17,10 +17,12 @@
|
||||
package org.springframework.cloud.consul.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.ecwid.consul.v1.ConsulClient;
|
||||
|
||||
@@ -32,13 +34,21 @@ import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.cloud.consul.ConsulAutoConfiguration;
|
||||
import org.springframework.cloud.consul.ConsulProperties;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import static org.springframework.cloud.consul.config.ConsulConfigProperties.Format.FILES;
|
||||
|
||||
public class ConsulConfigDataLocationResolver
|
||||
implements ConfigDataLocationResolver<ConsulConfigDataLocation> {
|
||||
|
||||
protected static final List<String> DIR_SUFFIXES = Collections.singletonList("/");
|
||||
|
||||
protected static final List<String> FILES_SUFFIXES = Collections
|
||||
.unmodifiableList(Arrays.asList(".yml", ".yaml", ".properties"));
|
||||
|
||||
@Override
|
||||
public boolean isResolvable(ConfigDataLocationResolverContext context,
|
||||
String location) {
|
||||
@@ -62,7 +72,10 @@ public class ConsulConfigDataLocationResolver
|
||||
ConfigDataLocationResolverContext context, String location, boolean optional,
|
||||
Profiles profiles) throws ConfigDataLocationNotFoundException {
|
||||
|
||||
ConsulConfigProperties properties = loadConfigProperties(context.getBinder());
|
||||
UriComponents locationUri = parseLocation(context, location);
|
||||
|
||||
ConsulConfigProperties properties = loadConfigProperties(context.getBinder(),
|
||||
locationUri);
|
||||
|
||||
String appName = properties.getName();
|
||||
if (StringUtils.isEmpty(appName)) {
|
||||
@@ -70,74 +83,68 @@ public class ConsulConfigDataLocationResolver
|
||||
.orElse("application");
|
||||
}
|
||||
|
||||
String prefix = properties.getPrefix();
|
||||
List<String> suffixes = new ArrayList<>();
|
||||
if (properties.getFormat() != FILES) {
|
||||
suffixes.add("/");
|
||||
}
|
||||
else {
|
||||
suffixes.add(".yml");
|
||||
suffixes.add(".yaml");
|
||||
suffixes.add(".properties");
|
||||
List<String> contexts = (CollectionUtils.isEmpty(locationUri.getPathSegments()))
|
||||
? getAutomaticContexts(profiles, properties, appName)
|
||||
: getCustomContexts(locationUri, properties);
|
||||
|
||||
registerBean(context, ConsulClient.class,
|
||||
() -> createConsulClient(context, locationUri));
|
||||
|
||||
registerBean(context, ConsulConfigIndexes.class, ConsulConfigDataIndexes::new);
|
||||
|
||||
return contexts.stream()
|
||||
.map(propertySourceContext -> new ConsulConfigDataLocation(properties,
|
||||
propertySourceContext, optional))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<String> getCustomContexts(UriComponents uriComponents,
|
||||
ConsulConfigProperties properties) {
|
||||
List<String> contexts = new ArrayList<>();
|
||||
if (StringUtils.isEmpty(uriComponents.getPath())) {
|
||||
return contexts;
|
||||
}
|
||||
|
||||
String defaultContext = getContext(prefix, properties.getDefaultContext());
|
||||
for (String path : uriComponents.getPath().split(";")) {
|
||||
for (String suffix : getSuffixes(properties)) {
|
||||
contexts.add(path + suffix);
|
||||
}
|
||||
}
|
||||
|
||||
return contexts;
|
||||
}
|
||||
|
||||
protected List<String> getSuffixes(ConsulConfigProperties properties) {
|
||||
if (properties.getFormat() == FILES) {
|
||||
return FILES_SUFFIXES;
|
||||
}
|
||||
return DIR_SUFFIXES;
|
||||
}
|
||||
|
||||
protected List<String> getAutomaticContexts(Profiles profiles,
|
||||
ConsulConfigProperties properties, String appName) {
|
||||
List<String> contexts = new ArrayList<>();
|
||||
|
||||
for (String suffix : suffixes) {
|
||||
String prefix = properties.getPrefix();
|
||||
String defaultContext = getContext(prefix, properties.getDefaultContext());
|
||||
for (String suffix : getSuffixes(properties)) {
|
||||
contexts.add(defaultContext + suffix);
|
||||
}
|
||||
for (String suffix : suffixes) {
|
||||
for (String suffix : getSuffixes(properties)) {
|
||||
addProfiles(contexts, defaultContext, profiles, suffix, properties);
|
||||
}
|
||||
|
||||
String baseContext = getContext(prefix, appName);
|
||||
|
||||
for (String suffix : suffixes) {
|
||||
for (String suffix : getSuffixes(properties)) {
|
||||
contexts.add(baseContext + suffix);
|
||||
}
|
||||
for (String suffix : suffixes) {
|
||||
for (String suffix : getSuffixes(properties)) {
|
||||
addProfiles(contexts, baseContext, profiles, suffix, properties);
|
||||
}
|
||||
|
||||
// we build them backwards, first wins, so reverse
|
||||
Collections.reverse(contexts);
|
||||
|
||||
registerBean(context, ConsulClient.class, () -> createConsulClient(context, location));
|
||||
|
||||
registerBean(context, ConsulConfigIndexes.class, ConsulConfigDataIndexes::new);
|
||||
|
||||
ArrayList<ConsulConfigDataLocation> locations = new ArrayList<>();
|
||||
contexts.forEach(
|
||||
propertySourceContext -> locations.add(new ConsulConfigDataLocation(
|
||||
properties, propertySourceContext, optional)));
|
||||
|
||||
return locations;
|
||||
}
|
||||
|
||||
protected <T> void registerBean(ConfigDataLocationResolverContext context,
|
||||
Class<T> type, Supplier<T> supplier) {
|
||||
context.getBootstrapRegistry().register(type, supplier)
|
||||
.onApplicationContextPrepared(
|
||||
(ctxt, consulClient) -> ctxt.getBeanFactory().registerSingleton(
|
||||
"configData" + type.getSimpleName(), consulClient));
|
||||
}
|
||||
|
||||
protected ConsulClient createConsulClient(ConfigDataLocationResolverContext context, String location) {
|
||||
ConsulProperties properties = loadProperties(context.getBinder());
|
||||
|
||||
String hostPort = location.substring("consul:".length());
|
||||
if (StringUtils.hasText(hostPort)) {
|
||||
String[] split = hostPort.split(":");
|
||||
if (split.length == 2) { // host and port
|
||||
properties.setHost(split[0]);
|
||||
properties.setPort(Integer.parseInt(split[1]));
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: add support for ConsulConfigProperties.prefix
|
||||
|
||||
return ConsulAutoConfiguration.createConsulClient(properties);
|
||||
return contexts;
|
||||
}
|
||||
|
||||
protected String getContext(String prefix, String context) {
|
||||
@@ -157,12 +164,52 @@ public class ConsulConfigDataLocationResolver
|
||||
}
|
||||
}
|
||||
|
||||
protected ConsulProperties loadProperties(Binder binder) {
|
||||
return binder.bind(ConsulProperties.PREFIX, Bindable.of(ConsulProperties.class))
|
||||
.orElse(new ConsulProperties());
|
||||
protected UriComponents parseLocation(ConfigDataLocationResolverContext context,
|
||||
String location) {
|
||||
String uri = location.substring("consul:".length());
|
||||
if (!StringUtils.hasText(uri)) {
|
||||
return null;
|
||||
}
|
||||
if (!uri.startsWith("//")) {
|
||||
uri = "consul:" + "//" + uri;
|
||||
}
|
||||
else {
|
||||
uri = location;
|
||||
}
|
||||
return UriComponentsBuilder.fromUriString(uri).build();
|
||||
}
|
||||
|
||||
protected ConsulConfigProperties loadConfigProperties(Binder binder) {
|
||||
protected <T> void registerBean(ConfigDataLocationResolverContext context,
|
||||
Class<T> type, Supplier<T> supplier) {
|
||||
context.getBootstrapRegistry().register(type, supplier)
|
||||
.onApplicationContextPrepared(
|
||||
(ctxt, consulClient) -> ctxt.getBeanFactory().registerSingleton(
|
||||
"configData" + type.getSimpleName(), consulClient));
|
||||
}
|
||||
|
||||
protected ConsulClient createConsulClient(ConfigDataLocationResolverContext context,
|
||||
UriComponents location) {
|
||||
ConsulProperties properties = loadProperties(context.getBinder(), location);
|
||||
return ConsulAutoConfiguration.createConsulClient(properties);
|
||||
}
|
||||
|
||||
protected ConsulProperties loadProperties(Binder binder, UriComponents location) {
|
||||
ConsulProperties consulProperties = binder
|
||||
.bind(ConsulProperties.PREFIX, Bindable.of(ConsulProperties.class))
|
||||
.orElse(new ConsulProperties());
|
||||
|
||||
if (StringUtils.hasText(location.getHost())) {
|
||||
consulProperties.setHost(location.getHost());
|
||||
}
|
||||
if (location.getPort() >= 0) {
|
||||
consulProperties.setPort(location.getPort());
|
||||
}
|
||||
|
||||
return consulProperties;
|
||||
}
|
||||
|
||||
protected ConsulConfigProperties loadConfigProperties(Binder binder,
|
||||
UriComponents location) {
|
||||
return binder
|
||||
.bind(ConsulConfigProperties.PREFIX,
|
||||
Bindable.of(ConsulConfigProperties.class))
|
||||
|
||||
@@ -89,8 +89,8 @@ public class ConsulConfigDataIntegrationTests {
|
||||
|
||||
context = new SpringApplicationBuilder(Config.class).web(WebApplicationType.NONE)
|
||||
.run("--spring.application.name=" + APP_NAME,
|
||||
"--spring.config.import=consul:" + ConsulTestcontainers.getHost() + ":" +
|
||||
ConsulTestcontainers.getPort(),
|
||||
"--spring.config.import=consul:" + ConsulTestcontainers.getHost()
|
||||
+ ":" + ConsulTestcontainers.getPort(),
|
||||
"--spring.cloud.consul.config.prefix=" + ROOT,
|
||||
"--spring.cloud.consul.config.watch.delay=10");
|
||||
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2015-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.consul.config;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.context.config.ConfigDataLocationResolverContext;
|
||||
import org.springframework.boot.context.config.Profiles;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.cloud.consul.ConsulProperties;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class ConsulConfigDataLocationResolverTests {
|
||||
|
||||
@Test
|
||||
public void testParseLocation() {
|
||||
ConsulConfigDataLocationResolver resolver = new ConsulConfigDataLocationResolver();
|
||||
UriComponents uriComponents = resolver.parseLocation(null,
|
||||
"consul:myhost:8501/mypath1;/mypath2;/mypath3");
|
||||
assertThat(uriComponents.toUri()).hasScheme("consul").hasHost("myhost")
|
||||
.hasPort(8501).hasPath("/mypath1;/mypath2;/mypath3");
|
||||
|
||||
uriComponents = resolver.parseLocation(null, "consul:myhost:8501");
|
||||
assertThat(uriComponents.toUri()).hasScheme("consul").hasHost("myhost")
|
||||
.hasPort(8501).hasPath("");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveProfileSpecificWithCustomPaths() {
|
||||
String location = "consul:myhost:8501/mypath1;/mypath2;/mypath3";
|
||||
List<ConsulConfigDataLocation> locations = testResolveProfileSpecific(location);
|
||||
assertThat(locations).hasSize(3);
|
||||
assertThat(toContexts(locations)).containsExactly("/mypath1/", "/mypath2/",
|
||||
"/mypath3/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveProfileSpecificWithAutomaticPaths() {
|
||||
String location = "consul:myhost";
|
||||
List<ConsulConfigDataLocation> locations = testResolveProfileSpecific(location);
|
||||
assertThat(locations).hasSize(4);
|
||||
assertThat(toContexts(locations)).containsExactly("config/testapp,dev/",
|
||||
"config/testapp/", "config/application,dev/", "config/application/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadProperties() {
|
||||
ConsulProperties properties = createResolver().loadProperties(
|
||||
Binder.get(new MockEnvironment()),
|
||||
UriComponentsBuilder.fromUriString("consul://myhost:8502").build());
|
||||
assertThat(properties.getHost()).isEqualTo("myhost");
|
||||
assertThat(properties.getPort()).isEqualTo(8502);
|
||||
}
|
||||
|
||||
private List<String> toContexts(List<ConsulConfigDataLocation> locations) {
|
||||
return locations.stream().map(ConsulConfigDataLocation::getContext)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<ConsulConfigDataLocation> testResolveProfileSpecific(String location) {
|
||||
ConsulConfigDataLocationResolver resolver = createResolver();
|
||||
ConfigDataLocationResolverContext context = mock(
|
||||
ConfigDataLocationResolverContext.class);
|
||||
MockEnvironment env = new MockEnvironment();
|
||||
env.setProperty("spring.application.name", "testapp");
|
||||
when(context.getBinder()).thenReturn(Binder.get(env));
|
||||
Profiles profiles = mock(Profiles.class);
|
||||
when(profiles.getAccepted()).thenReturn(Collections.singletonList("dev"));
|
||||
return resolver.resolveProfileSpecific(context, location, false, profiles);
|
||||
}
|
||||
|
||||
private ConsulConfigDataLocationResolver createResolver() {
|
||||
ConsulConfigDataLocationResolver resolver = new ConsulConfigDataLocationResolver() {
|
||||
@Override
|
||||
protected <T> void registerBean(ConfigDataLocationResolverContext context,
|
||||
Class<T> type, Supplier<T> supplier) {
|
||||
// do nothing
|
||||
}
|
||||
};
|
||||
return resolver;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user