Adds support for spring.config.import=consul: (#672)
This commit is contained in:
@@ -127,85 +127,88 @@ public class ConfigWatch implements ApplicationEventPublisherAware, SmartLifecyc
|
||||
|
||||
@Timed("consul.watch-config-keys")
|
||||
public void watchConfigKeyValues() {
|
||||
if (this.running.get()) {
|
||||
for (String context : this.consulIndexes.keySet()) {
|
||||
if (!this.running.get()) {
|
||||
return;
|
||||
}
|
||||
for (String context : this.consulIndexes.keySet()) {
|
||||
|
||||
// turn the context into a Consul folder path (unless our config format
|
||||
// are FILES)
|
||||
if (this.properties.getFormat() != FILES && !context.endsWith("/")) {
|
||||
context = context + "/";
|
||||
// turn the context into a Consul folder path (unless our config format
|
||||
// are FILES)
|
||||
if (this.properties.getFormat() != FILES && !context.endsWith("/")) {
|
||||
context = context + "/";
|
||||
}
|
||||
|
||||
try {
|
||||
Long currentIndex = this.consulIndexes.get(context);
|
||||
if (currentIndex == null) {
|
||||
currentIndex = -1L;
|
||||
}
|
||||
|
||||
try {
|
||||
Long currentIndex = this.consulIndexes.get(context);
|
||||
if (currentIndex == null) {
|
||||
currentIndex = -1L;
|
||||
}
|
||||
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("watching consul for context '" + context + "' with index "
|
||||
+ currentIndex);
|
||||
}
|
||||
|
||||
// use the consul ACL token if found
|
||||
String aclToken = this.properties.getAclToken();
|
||||
if (StringUtils.isEmpty(aclToken)) {
|
||||
aclToken = null;
|
||||
}
|
||||
// use the consul ACL token if found
|
||||
String aclToken = this.properties.getAclToken();
|
||||
if (StringUtils.isEmpty(aclToken)) {
|
||||
aclToken = null;
|
||||
}
|
||||
|
||||
Response<List<GetValue>> response = this.consul.getKVValues(context,
|
||||
aclToken,
|
||||
new QueryParams(this.properties.getWatch().getWaitTime(),
|
||||
currentIndex));
|
||||
Response<List<GetValue>> response = this.consul.getKVValues(context,
|
||||
aclToken, new QueryParams(
|
||||
this.properties.getWatch().getWaitTime(), currentIndex));
|
||||
|
||||
// if response.value == null, response was a 404, otherwise it was a
|
||||
// 200
|
||||
// reducing churn if there wasn't anything
|
||||
if (response.getValue() != null && !response.getValue().isEmpty()) {
|
||||
Long newIndex = response.getConsulIndex();
|
||||
// if response.value == null, response was a 404, otherwise it was a
|
||||
// 200, reducing churn if there wasn't anything
|
||||
if (response.getValue() != null && !response.getValue().isEmpty()) {
|
||||
Long newIndex = response.getConsulIndex();
|
||||
|
||||
if (newIndex != null && !newIndex.equals(currentIndex)) {
|
||||
// don't publish the same index again, don't publish the first
|
||||
// time (-1) so index can be primed
|
||||
if (!this.consulIndexes.containsValue(newIndex)
|
||||
&& !currentIndex.equals(-1L)) {
|
||||
if (newIndex != null && !newIndex.equals(currentIndex)) {
|
||||
// don't publish the same index again, don't publish the first
|
||||
// time (-1) so index can be primed
|
||||
if (!this.consulIndexes.containsValue(newIndex)
|
||||
&& !currentIndex.equals(-1L)) {
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("Context " + context + " has new index "
|
||||
+ newIndex);
|
||||
RefreshEventData data = new RefreshEventData(context,
|
||||
currentIndex, newIndex);
|
||||
this.publisher.publishEvent(
|
||||
new RefreshEvent(this, data, data.toString()));
|
||||
}
|
||||
else if (log.isTraceEnabled()) {
|
||||
log.trace("Event for index already published for context "
|
||||
+ context);
|
||||
}
|
||||
this.consulIndexes.put(context, newIndex);
|
||||
RefreshEventData data = new RefreshEventData(context,
|
||||
currentIndex, newIndex);
|
||||
this.publisher.publishEvent(
|
||||
new RefreshEvent(this, data, data.toString()));
|
||||
}
|
||||
else if (log.isTraceEnabled()) {
|
||||
log.trace("Same index for context " + context);
|
||||
log.trace("Event for index already published for context "
|
||||
+ context);
|
||||
}
|
||||
this.consulIndexes.put(context, newIndex);
|
||||
}
|
||||
else if (log.isTraceEnabled()) {
|
||||
log.trace("No value for context " + context);
|
||||
log.trace("Same index for context " + context);
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception e) {
|
||||
// only fail fast on the initial query, otherwise just log the error
|
||||
if (this.firstTime && this.properties.isFailFast()) {
|
||||
log.error(
|
||||
"Fail fast is set and there was an error reading configuration from consul.");
|
||||
ReflectionUtils.rethrowRuntimeException(e);
|
||||
}
|
||||
else if (log.isTraceEnabled()) {
|
||||
log.trace("Error querying consul Key/Values for context '"
|
||||
+ context + "'", e);
|
||||
}
|
||||
else if (log.isWarnEnabled()) {
|
||||
// simplified one line log message in the event of an agent
|
||||
// failure
|
||||
log.warn("Error querying consul Key/Values for context '"
|
||||
+ context + "'. Message: " + e.getMessage());
|
||||
}
|
||||
else if (log.isTraceEnabled()) {
|
||||
log.trace("No value for context " + context);
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception e) {
|
||||
// only fail fast on the initial query, otherwise just log the error
|
||||
if (this.firstTime && this.properties.isFailFast()) {
|
||||
log.error(
|
||||
"Fail fast is set and there was an error reading configuration from consul.");
|
||||
ReflectionUtils.rethrowRuntimeException(e);
|
||||
}
|
||||
else if (log.isTraceEnabled()) {
|
||||
log.trace("Error querying consul Key/Values for context '" + context
|
||||
+ "'", e);
|
||||
}
|
||||
else if (log.isWarnEnabled()) {
|
||||
// simplified one line log message in the event of an agent
|
||||
// failure
|
||||
log.warn("Error querying consul Key/Values for context '" + context
|
||||
+ "'. Message: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,11 @@ package org.springframework.cloud.consul.config;
|
||||
import com.ecwid.consul.v1.ConsulClient;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
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.consul.ConditionalOnConsulEnabled;
|
||||
import org.springframework.cloud.endpoint.RefreshEndpoint;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -34,6 +37,7 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnConsulEnabled
|
||||
@ConditionalOnProperty(name = "spring.cloud.consul.config.enabled", matchIfMissing = true)
|
||||
@EnableConfigurationProperties
|
||||
public class ConsulConfigAutoConfiguration {
|
||||
|
||||
/**
|
||||
@@ -41,23 +45,28 @@ public class ConsulConfigAutoConfiguration {
|
||||
*/
|
||||
public static final String CONFIG_WATCH_TASK_SCHEDULER_NAME = "configWatchTaskScheduler";
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public ConsulConfigProperties consulConfigProperties() {
|
||||
return new ConsulConfigProperties();
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(RefreshEndpoint.class)
|
||||
@ConditionalOnProperty(name = "spring.cloud.consul.config.watch.enabled",
|
||||
matchIfMissing = true)
|
||||
protected static class ConsulRefreshConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "spring.cloud.consul.config.watch.enabled",
|
||||
matchIfMissing = true)
|
||||
@ConditionalOnBean(ConsulConfigIndexes.class)
|
||||
public ConfigWatch configWatch(ConsulConfigProperties properties,
|
||||
ConsulPropertySourceLocator locator, ConsulClient consul,
|
||||
ConsulConfigIndexes indexes, ConsulClient consul,
|
||||
@Qualifier(CONFIG_WATCH_TASK_SCHEDULER_NAME) TaskScheduler taskScheduler) {
|
||||
return new ConfigWatch(properties, consul, locator.getContextIndexes(),
|
||||
return new ConfigWatch(properties, consul, indexes.getIndexes(),
|
||||
taskScheduler);
|
||||
}
|
||||
|
||||
@Bean(name = CONFIG_WATCH_TASK_SCHEDULER_NAME)
|
||||
@ConditionalOnProperty(name = "spring.cloud.consul.config.watch.enabled",
|
||||
matchIfMissing = true)
|
||||
public TaskScheduler configWatchTaskScheduler() {
|
||||
return new ThreadPoolTaskScheduler();
|
||||
}
|
||||
|
||||
@@ -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 com.ecwid.consul.v1.ConsulClient;
|
||||
import com.ecwid.consul.v1.Response;
|
||||
import com.ecwid.consul.v1.kv.model.GetValue;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.boot.context.config.ConfigData;
|
||||
import org.springframework.boot.context.config.ConfigDataLoader;
|
||||
import org.springframework.boot.context.config.ConfigDataLoaderContext;
|
||||
import org.springframework.boot.context.config.ConfigDataLocationNotFoundException;
|
||||
import org.springframework.boot.env.BootstrapRegistry.Registration;
|
||||
|
||||
import static org.springframework.cloud.consul.config.ConsulConfigProperties.Format.FILES;
|
||||
|
||||
public class ConsulConfigDataLoader
|
||||
implements ConfigDataLoader<ConsulConfigDataLocation> {
|
||||
|
||||
private static final Log log = LogFactory.getLog(ConsulConfigDataLoader.class);
|
||||
|
||||
@Override
|
||||
public ConfigData load(ConfigDataLoaderContext context,
|
||||
ConsulConfigDataLocation location) {
|
||||
try {
|
||||
ConsulClient consul = getBean(context, ConsulClient.class);
|
||||
ConsulConfigProperties properties = location.getProperties();
|
||||
ConsulPropertySource propertySource = null;
|
||||
|
||||
if (properties.getFormat() == FILES) {
|
||||
Response<GetValue> response = consul.getKVValue(location.getContext(),
|
||||
properties.getAclToken());
|
||||
addIndex(context, location, response.getConsulIndex());
|
||||
if (response.getValue() != null) {
|
||||
ConsulFilesPropertySource filesPropertySource = new ConsulFilesPropertySource(
|
||||
location.getContext(), consul, properties);
|
||||
filesPropertySource.init(response.getValue());
|
||||
propertySource = filesPropertySource;
|
||||
}
|
||||
else if (!location.isOptional()) {
|
||||
throw new ConfigDataLocationNotFoundException(location);
|
||||
}
|
||||
}
|
||||
else {
|
||||
propertySource = create(context, location);
|
||||
}
|
||||
return new ConfigData(Collections.singletonList(propertySource));
|
||||
}
|
||||
catch (ConfigDataLocationNotFoundException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (location.getProperties().isFailFast() || !location.isOptional()) {
|
||||
throw new ConfigDataLocationNotFoundException(location, e);
|
||||
}
|
||||
else {
|
||||
log.warn("Unable to load consul config from " + location.getContext(), e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected <T> T getBean(ConfigDataLoaderContext context, Class<T> type) {
|
||||
Registration<T> registration = context.getBootstrapRegistry()
|
||||
.getRegistration(type);
|
||||
if (registration == null) {
|
||||
return null;
|
||||
}
|
||||
return registration.get();
|
||||
}
|
||||
|
||||
protected ConsulPropertySource create(ConfigDataLoaderContext context,
|
||||
ConsulConfigDataLocation location) {
|
||||
ConsulPropertySource propertySource = new ConsulPropertySource(
|
||||
location.getContext(), getBean(context, ConsulClient.class),
|
||||
location.getProperties());
|
||||
propertySource.init();
|
||||
addIndex(context, location, propertySource.getInitialIndex());
|
||||
return propertySource;
|
||||
}
|
||||
|
||||
private void addIndex(ConfigDataLoaderContext context,
|
||||
ConsulConfigDataLocation location, Long consulIndex) {
|
||||
ConsulConfigIndexes indexes = getBean(context, ConsulConfigIndexes.class);
|
||||
if (indexes != null) { // should never be the case
|
||||
indexes.getIndexes().put(location.getContext(), consulIndex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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.Objects;
|
||||
|
||||
import org.springframework.boot.context.config.ConfigDataLocation;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
|
||||
public class ConsulConfigDataLocation extends ConfigDataLocation {
|
||||
|
||||
private final ConsulConfigProperties properties;
|
||||
|
||||
private final String context;
|
||||
|
||||
private final boolean optional;
|
||||
|
||||
public ConsulConfigDataLocation(ConsulConfigProperties properties, String context,
|
||||
boolean optional) {
|
||||
this.properties = properties;
|
||||
this.context = context;
|
||||
this.optional = optional;
|
||||
}
|
||||
|
||||
public ConsulConfigProperties getProperties() {
|
||||
return this.properties;
|
||||
}
|
||||
|
||||
public String getContext() {
|
||||
return this.context;
|
||||
}
|
||||
|
||||
public boolean isOptional() {
|
||||
return this.optional;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ConsulConfigDataLocation that = (ConsulConfigDataLocation) o;
|
||||
return this.optional == that.optional && this.properties.equals(that.properties)
|
||||
&& this.context.equals(that.context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.properties, this.context, this.optional);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringCreator(this).append("context", context)
|
||||
.append("optional", optional).append("properties", properties).toString();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* 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.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
|
||||
import com.ecwid.consul.v1.ConsulClient;
|
||||
|
||||
import org.springframework.boot.context.config.ConfigDataLocationNotFoundException;
|
||||
import org.springframework.boot.context.config.ConfigDataLocationResolver;
|
||||
import org.springframework.boot.context.config.ConfigDataLocationResolverContext;
|
||||
import org.springframework.boot.context.config.Profiles;
|
||||
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.StringUtils;
|
||||
|
||||
import static org.springframework.cloud.consul.config.ConsulConfigProperties.Format.FILES;
|
||||
|
||||
public class ConsulConfigDataLocationResolver
|
||||
implements ConfigDataLocationResolver<ConsulConfigDataLocation> {
|
||||
|
||||
@Override
|
||||
public boolean isResolvable(ConfigDataLocationResolverContext context,
|
||||
String location) {
|
||||
boolean enabled = context.getBinder()
|
||||
.bind(ConsulProperties.PREFIX + ".enabled", Boolean.class).orElse(true);
|
||||
boolean configEnabled = context.getBinder()
|
||||
.bind(ConsulConfigProperties.PREFIX + ".enabled", Boolean.class)
|
||||
.orElse(true);
|
||||
return location.startsWith("consul:") && configEnabled && enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ConsulConfigDataLocation> resolve(
|
||||
ConfigDataLocationResolverContext context, String location, boolean optional)
|
||||
throws ConfigDataLocationNotFoundException {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ConsulConfigDataLocation> resolveProfileSpecific(
|
||||
ConfigDataLocationResolverContext context, String location, boolean optional,
|
||||
Profiles profiles) throws ConfigDataLocationNotFoundException {
|
||||
//TODO: add support for consul host and port from location
|
||||
ConsulConfigProperties properties = loadConfigProperties(context.getBinder());
|
||||
|
||||
String appName = properties.getName();
|
||||
if (StringUtils.isEmpty(appName)) {
|
||||
appName = context.getBinder().bind("spring.application.name", String.class)
|
||||
.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");
|
||||
}
|
||||
|
||||
String defaultContext = getContext(prefix, properties.getDefaultContext());
|
||||
|
||||
List<String> contexts = new ArrayList<>();
|
||||
|
||||
for (String suffix : suffixes) {
|
||||
contexts.add(defaultContext + suffix);
|
||||
}
|
||||
for (String suffix : suffixes) {
|
||||
addProfiles(contexts, defaultContext, profiles, suffix, properties);
|
||||
}
|
||||
|
||||
String baseContext = getContext(prefix, appName);
|
||||
|
||||
for (String suffix : suffixes) {
|
||||
contexts.add(baseContext + suffix);
|
||||
}
|
||||
for (String suffix : suffixes) {
|
||||
addProfiles(contexts, baseContext, profiles, suffix, properties);
|
||||
}
|
||||
|
||||
Collections.reverse(contexts);
|
||||
|
||||
// TODO use location for host:port
|
||||
ConsulClient consul = createConsulClient(context);
|
||||
|
||||
registerBean(context, ConsulClient.class, consul);
|
||||
|
||||
ConsulConfigDataIndexes indexes = new ConsulConfigDataIndexes();
|
||||
|
||||
registerBean(context, ConsulConfigIndexes.class, indexes);
|
||||
|
||||
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, T instance) {
|
||||
context.getBootstrapRegistry().register(type, () -> instance)
|
||||
.onApplicationContextPrepared(
|
||||
(ctxt, consulClient) -> ctxt.getBeanFactory().registerSingleton(
|
||||
"configData" + type.getSimpleName(), consulClient));
|
||||
}
|
||||
|
||||
protected ConsulClient createConsulClient(ConfigDataLocationResolverContext context) {
|
||||
return ConsulAutoConfiguration
|
||||
.createConsulClient(loadProperties(context.getBinder()));
|
||||
}
|
||||
|
||||
protected String getContext(String prefix, String context) {
|
||||
if (StringUtils.isEmpty(prefix)) {
|
||||
return context;
|
||||
}
|
||||
else {
|
||||
return prefix + "/" + context;
|
||||
}
|
||||
}
|
||||
|
||||
protected void addProfiles(List<String> contexts, String baseContext,
|
||||
Profiles profiles, String suffix, ConsulConfigProperties properties) {
|
||||
for (String profile : profiles.getAccepted()) {
|
||||
contexts.add(
|
||||
baseContext + properties.getProfileSeparator() + profile + suffix);
|
||||
}
|
||||
}
|
||||
|
||||
protected ConsulProperties loadProperties(Binder binder) {
|
||||
return binder.bind(ConsulProperties.PREFIX, Bindable.of(ConsulProperties.class))
|
||||
.orElse(new ConsulProperties());
|
||||
}
|
||||
|
||||
protected ConsulConfigProperties loadConfigProperties(Binder binder) {
|
||||
return binder
|
||||
.bind(ConsulConfigProperties.PREFIX,
|
||||
Bindable.of(ConsulConfigProperties.class))
|
||||
.orElse(new ConsulConfigProperties());
|
||||
}
|
||||
|
||||
protected static class ConsulConfigDataIndexes implements ConsulConfigIndexes {
|
||||
|
||||
private final LinkedHashMap<String, Long> indexes = new LinkedHashMap<>();
|
||||
|
||||
@Override
|
||||
public LinkedHashMap<String, Long> getIndexes() {
|
||||
return indexes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.LinkedHashMap;
|
||||
|
||||
public interface ConsulConfigIndexes {
|
||||
|
||||
LinkedHashMap<String, Long> getIndexes();
|
||||
|
||||
}
|
||||
@@ -25,13 +25,20 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import static org.springframework.cloud.consul.config.ConsulConfigProperties.PREFIX;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@ConfigurationProperties("spring.cloud.consul.config")
|
||||
@ConfigurationProperties(PREFIX)
|
||||
@Validated
|
||||
public class ConsulConfigProperties {
|
||||
|
||||
/**
|
||||
* Prefix for configuration properties.
|
||||
*/
|
||||
public static final String PREFIX = "spring.cloud.consul.config";
|
||||
|
||||
private boolean enabled = true;
|
||||
|
||||
private String prefix = "config";
|
||||
|
||||
@@ -46,7 +46,8 @@ import static org.springframework.cloud.consul.config.ConsulConfigProperties.For
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@Order(0)
|
||||
public class ConsulPropertySourceLocator implements PropertySourceLocator {
|
||||
public class ConsulPropertySourceLocator
|
||||
implements PropertySourceLocator, ConsulConfigIndexes {
|
||||
|
||||
private static final Log log = LogFactory.getLog(ConsulPropertySourceLocator.class);
|
||||
|
||||
@@ -69,7 +70,8 @@ public class ConsulPropertySourceLocator implements PropertySourceLocator {
|
||||
return this.contexts;
|
||||
}
|
||||
|
||||
public LinkedHashMap<String, Long> getContextIndexes() {
|
||||
@Override
|
||||
public LinkedHashMap<String, Long> getIndexes() {
|
||||
return this.contextIndex;
|
||||
}
|
||||
|
||||
@@ -86,8 +88,7 @@ public class ConsulPropertySourceLocator implements PropertySourceLocator {
|
||||
ConfigurableEnvironment env = (ConfigurableEnvironment) environment;
|
||||
|
||||
String appName = this.properties.getName();
|
||||
|
||||
if (appName == null) {
|
||||
if (StringUtils.isEmpty(appName)) {
|
||||
appName = env.getProperty("spring.application.name");
|
||||
}
|
||||
|
||||
|
||||
@@ -4,3 +4,11 @@ org.springframework.cloud.consul.config.ConsulConfigAutoConfiguration
|
||||
# Bootstrap Configuration
|
||||
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
|
||||
org.springframework.cloud.consul.config.ConsulConfigBootstrapConfiguration
|
||||
|
||||
# ConfigData Location Resolvers
|
||||
org.springframework.boot.context.config.ConfigDataLocationResolver=\
|
||||
org.springframework.cloud.consul.config.ConsulConfigDataLocationResolver
|
||||
|
||||
# ConfigData Loaders
|
||||
org.springframework.boot.context.config.ConfigDataLoader=\
|
||||
org.springframework.cloud.consul.config.ConsulConfigDataLoader
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* 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 java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
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.cloud.context.environment.EnvironmentChangeEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
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 ConsulConfigDataIntegrationTests {
|
||||
|
||||
private static final String APP_NAME = "testConsulConfigData";
|
||||
|
||||
private static final String PREFIX = "_configDataIntegrationTests_config__";
|
||||
|
||||
private static final String ROOT = PREFIX + 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 = ROOT + "/application/" + TEST_PROP2;
|
||||
|
||||
private static final String TEST_PROP3 = "testProp3";
|
||||
|
||||
private static final String TEST_PROP3_CANONICAL = "test-prop3";
|
||||
|
||||
private static final String KEY3 = ROOT + "/" + APP_NAME + "/" + TEST_PROP3;
|
||||
|
||||
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.setKVValue(KEY1, VALUE1);
|
||||
client.setKVValue(KEY2, VALUE2);
|
||||
|
||||
context = new SpringApplicationBuilder(Config.class).web(WebApplicationType.NONE)
|
||||
.run("--spring.application.name=" + APP_NAME,
|
||||
"--spring.config.import=consul:", "--debug",
|
||||
"--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");
|
||||
|
||||
client = context.getBean(ConsulClient.class);
|
||||
environment = context.getEnvironment();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void teardown() {
|
||||
client.deleteKVValues(PREFIX);
|
||||
if (context != null) {
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void propertyLoaded() {
|
||||
String testProp = environment.getProperty(TEST_PROP2_CANONICAL);
|
||||
assertThat(testProp).as("testProp was wrong").isEqualTo(VALUE2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void propertyLoadedAndUpdated() throws Exception {
|
||||
String testProp = environment.getProperty(TEST_PROP_CANONICAL);
|
||||
assertThat(testProp).as("testProp was wrong").isEqualTo(VALUE1);
|
||||
|
||||
client.setKVValue(KEY1, "testPropValUpdate");
|
||||
|
||||
CountDownLatch latch = context.getBean("countDownLatch1", CountDownLatch.class);
|
||||
boolean receivedEvent = latch.await(15, TimeUnit.SECONDS);
|
||||
assertThat(receivedEvent).as("listener didn't receive event").isTrue();
|
||||
|
||||
testProp = environment.getProperty(TEST_PROP_CANONICAL);
|
||||
assertThat(testProp).as("testProp was wrong after update")
|
||||
.isEqualTo("testPropValUpdate");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contextDoesNotExistThenExists() throws Exception {
|
||||
String testProp = environment.getProperty(TEST_PROP3_CANONICAL);
|
||||
assertThat(testProp).as("testProp was wrong").isNull();
|
||||
|
||||
client.setKVValue(KEY3, "testPropValInsert");
|
||||
|
||||
CountDownLatch latch = context.getBean("countDownLatch2", CountDownLatch.class);
|
||||
boolean receivedEvent = latch.await(15, TimeUnit.SECONDS);
|
||||
assertThat(receivedEvent).as("listener didn't receive event").isTrue();
|
||||
|
||||
testProp = environment.getProperty(TEST_PROP3_CANONICAL);
|
||||
assertThat(testProp).as(TEST_PROP3 + " was wrong after update")
|
||||
.isEqualTo("testPropValInsert");
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
static class Config implements ApplicationListener<EnvironmentChangeEvent> {
|
||||
|
||||
@Bean
|
||||
public CountDownLatch countDownLatch1() {
|
||||
return new CountDownLatch(1);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CountDownLatch countDownLatch2() {
|
||||
return new CountDownLatch(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(EnvironmentChangeEvent event) {
|
||||
if (event.getKeys().contains(TEST_PROP)) {
|
||||
countDownLatch1().countDown();
|
||||
}
|
||||
else if (event.getKeys().contains(TEST_PROP3)) {
|
||||
countDownLatch2().countDown();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,20 +21,19 @@ import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.ecwid.consul.v1.ConsulClient;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
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.cloud.context.environment.EnvironmentChangeEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
|
||||
@@ -74,85 +73,81 @@ public class ConsulPropertySourceLocatorTests {
|
||||
|
||||
private static final String KEY3 = ROOT + "/" + APP_NAME + "/" + TEST_PROP3;
|
||||
|
||||
private ConfigurableApplicationContext context;
|
||||
private static ConfigurableApplicationContext context;
|
||||
|
||||
private ConfigurableEnvironment environment;
|
||||
private static ConfigurableEnvironment environment;
|
||||
|
||||
private ConsulClient client;
|
||||
private static ConsulClient client;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
@BeforeAll
|
||||
public static void setup() {
|
||||
ConsulTestcontainers.start();
|
||||
this.client = ConsulTestcontainers.client();
|
||||
this.client.deleteKVValues(PREFIX);
|
||||
this.client.setKVValue(KEY1, VALUE1);
|
||||
this.client.setKVValue(KEY2, VALUE2);
|
||||
client = ConsulTestcontainers.client();
|
||||
client.deleteKVValues(PREFIX);
|
||||
client.setKVValue(KEY1, VALUE1);
|
||||
client.setKVValue(KEY2, VALUE2);
|
||||
|
||||
this.context = new SpringApplicationBuilder(Config.class)
|
||||
.web(WebApplicationType.NONE).run("--SPRING_APPLICATION_NAME=" + APP_NAME,
|
||||
context = new SpringApplicationBuilder(Config.class).web(WebApplicationType.NONE)
|
||||
.run("--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.watch.delay=10");
|
||||
|
||||
this.client = this.context.getBean(ConsulClient.class);
|
||||
this.environment = this.context.getEnvironment();
|
||||
client = context.getBean(ConsulClient.class);
|
||||
environment = context.getEnvironment();
|
||||
}
|
||||
|
||||
@After
|
||||
public void teardown() {
|
||||
this.client.deleteKVValues(PREFIX);
|
||||
@AfterAll
|
||||
public static void teardown() {
|
||||
client.deleteKVValues(PREFIX);
|
||||
if (context != null) {
|
||||
this.context.close();
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void propertyLoaded() throws Exception {
|
||||
String testProp = this.environment.getProperty(TEST_PROP2_CANONICAL);
|
||||
public void propertyLoaded() {
|
||||
String testProp = environment.getProperty(TEST_PROP2_CANONICAL);
|
||||
assertThat(testProp).as("testProp was wrong").isEqualTo(VALUE2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore // FIXME: broken tests with boot 2.0.0
|
||||
public void propertyLoadedAndUpdated() throws Exception {
|
||||
String testProp = this.environment.getProperty(TEST_PROP_CANONICAL);
|
||||
String testProp = environment.getProperty(TEST_PROP_CANONICAL);
|
||||
assertThat(testProp).as("testProp was wrong").isEqualTo(VALUE1);
|
||||
|
||||
this.client.setKVValue(KEY1, "testPropValUpdate");
|
||||
client.setKVValue(KEY1, "testPropValUpdate");
|
||||
|
||||
CountDownLatch latch = this.context.getBean("countDownLatch1",
|
||||
CountDownLatch.class);
|
||||
CountDownLatch latch = context.getBean("countDownLatch1", CountDownLatch.class);
|
||||
boolean receivedEvent = latch.await(15, TimeUnit.SECONDS);
|
||||
assertThat(receivedEvent).as("listener didn't receive event").isTrue();
|
||||
|
||||
testProp = this.environment.getProperty(TEST_PROP_CANONICAL);
|
||||
testProp = environment.getProperty(TEST_PROP_CANONICAL);
|
||||
assertThat(testProp).as("testProp was wrong after update")
|
||||
.isEqualTo("testPropValUpdate");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore // FIXME: broken tests with boot 2.0.0
|
||||
public void contextDoesNotExistThenExists() throws Exception {
|
||||
String testProp = this.environment.getProperty(TEST_PROP3_CANONICAL);
|
||||
String testProp = environment.getProperty(TEST_PROP3_CANONICAL);
|
||||
assertThat(testProp).as("testProp was wrong").isNull();
|
||||
|
||||
this.client.setKVValue(KEY3, "testPropValInsert");
|
||||
client.setKVValue(KEY3, "testPropValInsert");
|
||||
|
||||
CountDownLatch latch = this.context.getBean("countDownLatch2",
|
||||
CountDownLatch.class);
|
||||
CountDownLatch latch = context.getBean("countDownLatch2", CountDownLatch.class);
|
||||
boolean receivedEvent = latch.await(15, TimeUnit.SECONDS);
|
||||
assertThat(receivedEvent).as("listener didn't receive event").isTrue();
|
||||
|
||||
testProp = this.environment.getProperty(TEST_PROP3_CANONICAL);
|
||||
testProp = environment.getProperty(TEST_PROP3_CANONICAL);
|
||||
assertThat(testProp).as(TEST_PROP3 + " was wrong after update")
|
||||
.isEqualTo("testPropValInsert");
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
static class Config {
|
||||
static class Config implements ApplicationListener<EnvironmentChangeEvent> {
|
||||
|
||||
@Bean
|
||||
public CountDownLatch countDownLatch1() {
|
||||
@@ -164,8 +159,8 @@ public class ConsulPropertySourceLocatorTests {
|
||||
return new CountDownLatch(1);
|
||||
}
|
||||
|
||||
@EventListener
|
||||
public void handle(EnvironmentChangeEvent event) {
|
||||
@Override
|
||||
public void onApplicationEvent(EnvironmentChangeEvent event) {
|
||||
if (event.getKeys().contains(TEST_PROP)) {
|
||||
countDownLatch1().countDown();
|
||||
}
|
||||
|
||||
@@ -54,6 +54,10 @@ public class ConsulAutoConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public ConsulClient consulClient(ConsulProperties consulProperties) {
|
||||
return createConsulClient(consulProperties);
|
||||
}
|
||||
|
||||
public static ConsulClient createConsulClient(ConsulProperties consulProperties) {
|
||||
final int agentPort = consulProperties.getPort();
|
||||
final String agentHost = !StringUtils.isEmpty(consulProperties.getScheme())
|
||||
? consulProperties.getScheme() + "://" + consulProperties.getHost()
|
||||
|
||||
Reference in New Issue
Block a user