Add support for multiple configuration path prefixes.

Fixes gh-450
This commit is contained in:
Colin Mullikin
2019-11-26 17:48:17 -06:00
committed by spencergibb
parent 30041b76c5
commit 3c4ceb0823
8 changed files with 176 additions and 29 deletions

View File

@@ -339,7 +339,7 @@ The watch uses a Spring `TaskScheduler` to schedule the call to consul. By defau
[[spring-cloud-consul-config]]
== Distributed Configuration with Consul
Consul provides a https://consul.io/docs/agent/http/kv.html[Key/Value Store] for storing configuration and other metadata. Spring Cloud Consul Config is an alternative to the https://github.com/spring-cloud/spring-cloud-config[Config Server and Client]. Configuration is loaded into the Spring Environment during the special "bootstrap" phase. Configuration is stored in the `/config` folder by default. Multiple `PropertySource` instances are created based on the application's name and the active profiles that mimicks the Spring Cloud Config order of resolving properties. For example, an application with the name "testApp" and with the "dev" profile will have the following property sources created:
Consul provides a https://consul.io/docs/agent/http/kv.html[Key/Value Store] for storing configuration and other metadata. Spring Cloud Consul Config is an alternative to the https://github.com/spring-cloud/spring-cloud-config[Config Server and Client]. Configuration is loaded into the Spring Environment during the special "bootstrap" phase. Configuration is stored in the `/config` folder by default. Multiple `PropertySource` instances are created based on the application's name and the active profiles that mimics the Spring Cloud Config order of resolving properties. For example, an application with the name "testApp" and with the "dev" profile will have the following property sources created:
----
config/testApp,dev/

View File

@@ -16,13 +16,19 @@
package org.springframework.cloud.consul.config;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.annotation.Validated;
import static org.springframework.cloud.consul.config.ConsulConfigProperties.PREFIX;
@@ -41,7 +47,7 @@ public class ConsulConfigProperties {
private boolean enabled = true;
private String prefix = "config";
private List<String> prefixes = new ArrayList<>(Collections.singletonList("config"));
@NotEmpty
private String defaultContext = "application";
@@ -92,12 +98,31 @@ public class ConsulConfigProperties {
this.enabled = enabled;
}
public String getPrefix() {
return this.prefix;
public List<String> getPrefixes() {
return this.prefixes;
}
public void setPrefixes(List<String> prefixes) {
this.prefixes = prefixes;
}
@DeprecatedConfigurationProperty(reason = "replaced to support multiple prefixes",
replacement = PREFIX + ".prefixes")
public String getPrefix() {
if (CollectionUtils.isEmpty(this.prefixes)) {
return null;
}
return this.prefixes.get(0);
}
@Deprecated
public void setPrefix(String prefix) {
this.prefix = prefix;
if (prefix != null) {
this.prefixes = new ArrayList<>(Collections.singletonList(prefix));
}
else {
this.prefixes = new ArrayList<>();
}
}
public @NotEmpty String getDefaultContext() {
@@ -166,7 +191,7 @@ public class ConsulConfigProperties {
@Override
public String toString() {
return new ToStringCreator(this).append("enabled", this.enabled).append("prefix", this.prefix)
return new ToStringCreator(this).append("enabled", this.enabled).append("prefixes", this.prefixes)
.append("defaultContext", this.defaultContext).append("profileSeparator", this.profileSeparator)
.append("format", this.format).append("dataKey", this.dataKey).append("aclToken", this.aclToken)
.append("watch", this.watch).append("failFast", this.failFast).append("name", this.name).toString();

View File

@@ -85,8 +85,8 @@ public class ConsulPropertySourceLocator implements PropertySourceLocator, Consu
CompositePropertySource composite = new CompositePropertySource("consul");
for (String propertySourceContext : this.contexts) {
ConsulPropertySource propertySource = sources.createPropertySource(propertySourceContext, true,
this.consul, contextIndex::put);
ConsulPropertySource propertySource = sources.createPropertySource(propertySourceContext, this.consul,
contextIndex::put);
if (propertySource != null) {
composite.addPropertySource(propertySource);
}

View File

@@ -53,24 +53,25 @@ public class ConsulPropertySources {
public List<String> getAutomaticContexts(List<String> profiles, boolean reverse) {
List<String> contexts = new ArrayList<>();
String prefix = properties.getPrefix();
String defaultContext = getContext(prefix, properties.getDefaultContext());
List<String> suffixes = getSuffixes();
for (String suffix : suffixes) {
contexts.add(defaultContext + suffix);
}
for (String suffix : suffixes) {
addProfiles(contexts, defaultContext, profiles, suffix);
}
for (String prefix : this.properties.getPrefixes()) {
String defaultContext = getContext(prefix, properties.getDefaultContext());
List<String> suffixes = getSuffixes();
for (String suffix : suffixes) {
contexts.add(defaultContext + suffix);
}
for (String suffix : suffixes) {
addProfiles(contexts, defaultContext, profiles, suffix);
}
// getName() defaults to ${spring.application.name} or application
String baseContext = getContext(prefix, properties.getName());
// getName() defaults to ${spring.application.name} or application
String baseContext = getContext(prefix, properties.getName());
for (String suffix : suffixes) {
contexts.add(baseContext + suffix);
}
for (String suffix : suffixes) {
addProfiles(contexts, baseContext, profiles, suffix);
for (String suffix : suffixes) {
contexts.add(baseContext + suffix);
}
for (String suffix : suffixes) {
addProfiles(contexts, baseContext, profiles, suffix);
}
}
if (reverse) {
// we build them backwards, first wins, so reverse

View File

@@ -16,6 +16,8 @@
package org.springframework.cloud.consul.config;
import java.util.Collections;
import org.junit.Test;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@@ -39,7 +41,7 @@ public class ConsulConfigBootstrapConfigurationTests {
this.contextRunner.withUserConfiguration(TestConfig.class).withInitializer(new ConsulTestcontainers())
.withUserConfiguration(ConsulConfigBootstrapConfiguration.class).run(context -> {
ConsulConfigProperties config = context.getBean(ConsulConfigProperties.class);
assertThat(config.getPrefix()).as("Prefix did not match").isEqualTo("platform-config");
assertThat(config.getPrefixes().get(0)).as("Prefix did not match").isEqualTo("platform-config");
assertThat(config.getDefaultContext()).as("Default context did not match").isEqualTo("defaults");
});
}
@@ -53,7 +55,7 @@ public class ConsulConfigBootstrapConfigurationTests {
this.contextRunner.withUserConfiguration(ConsulConfigBootstrapConfiguration.class)
.withInitializer(new ConsulTestcontainers()).run(context -> {
ConsulConfigProperties config = context.getBean(ConsulConfigProperties.class);
assertThat(config.getPrefix()).as("Prefix did not match").isEqualTo("config");
assertThat(config.getPrefixes().get(0)).as("Prefix did not match").isEqualTo("config");
assertThat(config.getDefaultContext()).as("Default context did not match").isEqualTo("application");
});
}
@@ -66,7 +68,7 @@ public class ConsulConfigBootstrapConfigurationTests {
@Bean
public ConsulConfigProperties consulConfigProperties() {
ConsulConfigProperties config = new ConsulConfigProperties();
config.setPrefix("platform-config");
config.setPrefixes(Collections.singletonList("platform-config"));
config.setDefaultContext("defaults");
return config;
}

View File

@@ -68,7 +68,7 @@ public class ConsulConfigDataCustomizationIntegrationTests {
context = application.run("--spring.application.name=" + APP_NAME,
"--spring.config.import=consul:" + ConsulTestcontainers.getHost() + ":"
+ ConsulTestcontainers.getPort(),
"--spring.cloud.consul.config.prefix=" + ROOT, "--spring.cloud.consul.config.watch.delay=10");
"--spring.cloud.consul.config.prefixes=" + ROOT, "--spring.cloud.consul.config.watch.delay=10");
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright 2013-2019 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.UUID;
import com.ecwid.consul.v1.ConsulClient;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.consul.test.ConsulTestcontainers;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.test.annotation.DirtiesContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Spencer Gibb
*/
@DirtiesContext
public class ConsulConfigDataMultiplePrefixesIntegrationTests {
private static final String APP_NAME = "testConsulConfigData";
private static final String PREFIX = "_configDataMultiplePrefixesIntegrationTests_config__";
private static final String PREFIX2 = "_configDataMultiplePrefixesIntegrationTests_config2__";
private static final String ROOT = PREFIX + UUID.randomUUID();
private static final String ROOT2 = PREFIX2 + UUID.randomUUID();
private static final String VALUE1 = "testPropVal";
private static final String TEST_PROP = "testProp";
private static final String TEST_PROP_CANONICAL = "test-prop";
private static final String KEY1 = ROOT + "/application/" + TEST_PROP;
private static final String VALUE2 = "testPropVal2";
private static final String TEST_PROP2 = "testProp2";
private static final String TEST_PROP2_CANONICAL = "test-prop2";
private static final String KEY2 = ROOT2 + "/application/" + TEST_PROP2;
private static ConfigurableApplicationContext context;
private static ConfigurableEnvironment environment;
private static ConsulClient client;
@BeforeAll
public static void setup() {
ConsulTestcontainers.start();
client = ConsulTestcontainers.client();
client.deleteKVValues(PREFIX);
client.deleteKVValues(PREFIX2);
client.setKVValue(KEY1, VALUE1);
client.setKVValue(KEY2, VALUE2);
context = new SpringApplicationBuilder(Config.class).web(WebApplicationType.NONE).run(
"--logging.level.org.springframework.cloud.consul.config.ConfigWatch=TRACE",
"--spring.application.name=" + APP_NAME,
"--spring.config.import=consul:" + ConsulTestcontainers.getHost() + ":"
+ ConsulTestcontainers.getPort(),
"--spring.cloud.consul.config.prefixes=" + ROOT + "," + ROOT2,
"--spring.cloud.consul.config.watch.delay=10", "--spring.cloud.consul.config.watch.wait-time=1");
client = context.getBean(ConsulClient.class);
environment = context.getEnvironment();
}
@AfterAll
public static void teardown() {
client.deleteKVValues(PREFIX);
client.deleteKVValues(PREFIX2);
if (context != null) {
context.close();
}
}
@Test
public void propertyLoaded() {
String testProp = environment.getProperty(TEST_PROP_CANONICAL);
assertThat(testProp).as(TEST_PROP + " was wrong").isEqualTo(VALUE1);
String testProp2 = environment.getProperty(TEST_PROP2_CANONICAL);
assertThat(testProp2).as(TEST_PROP2 + " was wrong").isEqualTo(VALUE2);
}
@Configuration
@EnableAutoConfiguration
static class Config {
}
}

View File

@@ -91,7 +91,7 @@ public class ConsulPropertySourceLocatorTests {
"--spring.application.name=" + APP_NAME, "--spring.config.use-legacy-processing=true",
"--spring.cloud.consul.host=" + ConsulTestcontainers.getHost(),
"--spring.cloud.consul.port=" + ConsulTestcontainers.getPort(),
"--spring.cloud.consul.config.prefix=" + ROOT, "--spring.cloud.consul.config.watch.delay=10");
"--spring.cloud.consul.config.prefixes=" + ROOT, "--spring.cloud.consul.config.watch.delay=10");
client = context.getBean(ConsulClient.class);
environment = context.getEnvironment();