Allow the use of multiple configmaps as sources
This commit is contained in:
committed by
Ioannis Canellos
parent
5166090b77
commit
a30b2d85dc
30
README.md
30
README.md
@@ -88,11 +88,37 @@ The [Spring Cloud Kubernetes Config](./spring-cloud-kubernetes-config) project m
|
||||
during application bootstrapping and triggers hot reloading of beans or Spring context when changes are detected on
|
||||
observed `ConfigMap`s.
|
||||
|
||||
`ConfigMapPropertySource` will search for a Kubernetes `ConfigMap` which `metadata.name` is either the name of
|
||||
The default behavior is to create a `ConfigMapPropertySource` based on a Kubernetes `ConfigMap` which has `metadata.name` of either the name of
|
||||
your Spring application (as defined by its `spring.application.name` property) or a custom name defined within the
|
||||
`bootstrap.properties` file under the following key `spring.cloud.kubernetes.config.name`.
|
||||
|
||||
If such a `ConfigMap` is found, it will be processed as follows:
|
||||
However, more advanced configuration are possible where multiple ConfigMaps can be used
|
||||
This is made possible by the `spring.cloud.kubernetes.config.sources` list.
|
||||
For example one could define the following ConfigMaps
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
application:
|
||||
name: cloud-k8s-app
|
||||
cloud:
|
||||
kubernetes:
|
||||
config:
|
||||
name: default-name
|
||||
namespace: default-namespace
|
||||
sources:
|
||||
# Spring Cloud Kubernetes will lookup a ConfigMap named c1 in namespace default-namespace
|
||||
- name: c1
|
||||
# Spring Cloud Kubernetes will lookup a ConfigMap named default-name in whatever namespace n2
|
||||
- namespace: n2
|
||||
# Spring Cloud Kubernetes will lookup a ConfigMap named c3 in namespace n3
|
||||
- namespace: n3
|
||||
name: c3
|
||||
```
|
||||
|
||||
In the example above, it `spring.cloud.kubernetes.config.namespace` had not been set,
|
||||
then the ConfigMap named `c1` would be looked up in the namespace that the application runs
|
||||
|
||||
Any matching `ConfigMap` that is found, will be processed as follows:
|
||||
|
||||
- apply individual configuration properties.
|
||||
- apply as `yaml` the content of any property named `application.yaml`
|
||||
|
||||
@@ -17,10 +17,12 @@
|
||||
|
||||
package org.springframework.cloud.kubernetes.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@ConfigurationProperties("spring.cloud.kubernetes.config")
|
||||
public class ConfigMapConfigProperties extends AbstractConfigProperties {
|
||||
@@ -29,6 +31,7 @@ public class ConfigMapConfigProperties extends AbstractConfigProperties {
|
||||
|
||||
private boolean enableApi = true;
|
||||
private List<String> paths = new LinkedList<>();
|
||||
private List<Source> sources = new LinkedList<>();
|
||||
|
||||
public boolean isEnableApi() {
|
||||
return enableApi;
|
||||
@@ -46,8 +49,101 @@ public class ConfigMapConfigProperties extends AbstractConfigProperties {
|
||||
return paths;
|
||||
}
|
||||
|
||||
public List<Source> getSources() {
|
||||
return sources;
|
||||
}
|
||||
|
||||
public void setSources(List<Source> sources) {
|
||||
this.sources = sources;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A list of Source to use
|
||||
* If the user has not specified any Source properties, then a single Source
|
||||
* is constructed based on the supplied name and namespace
|
||||
*
|
||||
* These are the actual name/namespace pairs that are used to create a ConfigMapPropertySource
|
||||
*/
|
||||
public List<NormalizedSource> determineSources() {
|
||||
if (sources.isEmpty()) {
|
||||
return new ArrayList<NormalizedSource>() {{
|
||||
add(new NormalizedSource(name, namespace));
|
||||
}};
|
||||
}
|
||||
|
||||
return sources.stream().map(s -> s.normalize(name, namespace)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConfigurationTarget() {
|
||||
return TARGET;
|
||||
}
|
||||
|
||||
public static class Source {
|
||||
|
||||
/**
|
||||
* The name of the ConfigMap
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* The namespace where the ConfigMap is found
|
||||
*/
|
||||
private String namespace;
|
||||
|
||||
public Source() {
|
||||
}
|
||||
|
||||
public Source(String name, String namespace) {
|
||||
this.name = name;
|
||||
this.namespace = namespace;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getNamespace() {
|
||||
return namespace;
|
||||
}
|
||||
|
||||
public void setNamespace(String namespace) {
|
||||
this.namespace = namespace;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return StringUtils.isEmpty(name) && StringUtils.isEmpty(namespace);
|
||||
}
|
||||
|
||||
public NormalizedSource normalize(String defaultName, String defaultNamespace) {
|
||||
final String normalizedName =
|
||||
StringUtils.isEmpty(this.name) ? defaultName : this.name;
|
||||
final String normalizedNamespace =
|
||||
StringUtils.isEmpty(this.namespace) ? defaultNamespace : this.namespace;
|
||||
|
||||
return new NormalizedSource(normalizedName, normalizedNamespace);
|
||||
}
|
||||
}
|
||||
|
||||
static class NormalizedSource {
|
||||
private final String name;
|
||||
private final String namespace;
|
||||
|
||||
public NormalizedSource(String name, String namespace) {
|
||||
this.name = name;
|
||||
this.namespace = namespace;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getNamespace() {
|
||||
return namespace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,13 +17,19 @@
|
||||
|
||||
package org.springframework.cloud.kubernetes.config;
|
||||
|
||||
import static org.springframework.cloud.kubernetes.config.ConfigUtils.getApplicationName;
|
||||
import static org.springframework.cloud.kubernetes.config.ConfigUtils.getApplicationNamespace;
|
||||
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import java.util.List;
|
||||
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
|
||||
import org.springframework.cloud.kubernetes.config.ConfigMapConfigProperties.NormalizedSource;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.env.CompositePropertySource;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
import static org.springframework.cloud.kubernetes.config.ConfigUtils.*;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
|
||||
@Order(0)
|
||||
public class ConfigMapPropertySourceLocator implements PropertySourceLocator {
|
||||
@@ -36,13 +42,36 @@ public class ConfigMapPropertySourceLocator implements PropertySourceLocator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapPropertySource locate(Environment environment) {
|
||||
public PropertySource locate(Environment environment) {
|
||||
if (environment instanceof ConfigurableEnvironment) {
|
||||
ConfigurableEnvironment env = (ConfigurableEnvironment) environment;
|
||||
String name = getApplicationName(environment, properties);
|
||||
String namespace = getApplicationNamespace(client, env, properties);
|
||||
return new ConfigMapPropertySource(client, name, namespace, env.getActiveProfiles(), properties);
|
||||
}
|
||||
|
||||
List<ConfigMapConfigProperties.NormalizedSource> sources =
|
||||
properties.determineSources();
|
||||
if (sources.size() == 1) {
|
||||
return getMapPropertySourceForSingleConfigMap(env, sources.get(0));
|
||||
}
|
||||
|
||||
CompositePropertySource composite = new CompositePropertySource("composite-configmap");
|
||||
sources.forEach(s ->
|
||||
composite.addFirstPropertySource(getMapPropertySourceForSingleConfigMap(env, s))
|
||||
);
|
||||
|
||||
return composite;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private MapPropertySource getMapPropertySourceForSingleConfigMap(
|
||||
ConfigurableEnvironment environment, NormalizedSource normalizedSource) {
|
||||
|
||||
String configurationTarget = properties.getConfigurationTarget();
|
||||
return new ConfigMapPropertySource(
|
||||
client,
|
||||
getApplicationName(environment, normalizedSource.getName(), configurationTarget),
|
||||
getApplicationNamespace(client, normalizedSource.getNamespace(), configurationTarget),
|
||||
environment.getActiveProfiles(),
|
||||
properties
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,12 +14,13 @@ public class ConfigUtils {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(ConfigUtils.class);
|
||||
|
||||
public static <C extends AbstractConfigProperties> String getApplicationName(Environment env, C config) {
|
||||
String name = config.getName();
|
||||
public static <C extends AbstractConfigProperties> String getApplicationName(Environment env,
|
||||
String configName, String configurationTarget) {
|
||||
String name = configName;
|
||||
if (StringUtils.isEmpty(name)) {
|
||||
//TODO: use relaxed binding
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug(config.getConfigurationTarget() +
|
||||
LOG.debug(configurationTarget +
|
||||
" name has not been set, taking it from property/env " +
|
||||
SPRING_APPLICATION_NAME + " (default=" + FALLBACK_APPLICATION_NAME + ")");
|
||||
}
|
||||
@@ -30,11 +31,13 @@ public class ConfigUtils {
|
||||
return name;
|
||||
}
|
||||
|
||||
public static <C extends AbstractConfigProperties> String getApplicationNamespace(KubernetesClient client, Environment env, C config) {
|
||||
String namespace = config.getNamespace();
|
||||
public static <C extends AbstractConfigProperties> String getApplicationNamespace(
|
||||
KubernetesClient client, String configNamespace, String configurationTarget) {
|
||||
String namespace = configNamespace;
|
||||
if (StringUtils.isEmpty(namespace)) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug(config.getConfigurationTarget() + " namespace has not been set, taking it from client (ns="+client.getNamespace()+")");
|
||||
LOG.debug(
|
||||
configurationTarget + " namespace has not been set, taking it from client (ns="+client.getNamespace()+")");
|
||||
}
|
||||
|
||||
namespace = client.getNamespace();
|
||||
|
||||
@@ -46,15 +46,17 @@ public class SecretsPropertySource extends KubernetesPropertySource {
|
||||
return new StringBuilder()
|
||||
.append(PREFIX)
|
||||
.append(Constants.PROPERTY_SOURCE_NAME_SEPARATOR)
|
||||
.append(getApplicationName(env,config))
|
||||
.append(getApplicationName(env, config.getName(), config.getConfigurationTarget()))
|
||||
.append(Constants.PROPERTY_SOURCE_NAME_SEPARATOR)
|
||||
.append(getApplicationNamespace(client, env, config))
|
||||
.append(getApplicationNamespace(client, config.getNamespace(),
|
||||
config.getConfigurationTarget()))
|
||||
.toString();
|
||||
}
|
||||
|
||||
private static Map<String, Object> getSourceData(KubernetesClient client, Environment env, SecretsConfigProperties config) {
|
||||
String name = getApplicationName(env, config);
|
||||
String namespace = getApplicationNamespace(client, env, config);
|
||||
String name = getApplicationName(env, config.getName(), config.getConfigurationTarget());
|
||||
String namespace = getApplicationNamespace(client, config.getNamespace(),
|
||||
config.getConfigurationTarget());
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
if (config.isEnableApi()) {
|
||||
|
||||
@@ -16,17 +16,19 @@
|
||||
*/
|
||||
package org.springframework.cloud.kubernetes.config.reload;
|
||||
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.annotation.PreDestroy;
|
||||
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
|
||||
import org.springframework.core.env.CompositePropertySource;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
|
||||
@@ -76,6 +78,23 @@ public abstract class ConfigurationChangeDetector {
|
||||
return s1 == null ? s2 != null : !s1.equals(s2);
|
||||
}
|
||||
|
||||
protected boolean changed(List<? extends MapPropertySource> l1,
|
||||
List<? extends MapPropertySource> l2) {
|
||||
|
||||
if(l1.size() != l2.size()) {
|
||||
log.debug("The current number of Confimap PropertySources does not match "
|
||||
+ "the ones loaded from the Kubernetes - No reload will take place");
|
||||
return false;
|
||||
}
|
||||
|
||||
for(int i=0; i<l1.size(); i++) {
|
||||
if (changed(l1.get(i), l2.get(i))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds one registered property source of the given type, logging a warning if
|
||||
* multiple property sources of that type are available.
|
||||
@@ -119,4 +138,30 @@ public abstract class ConfigurationChangeDetector {
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of MapPropertySource that correspond to the current state of the system
|
||||
* This only handles the PropertySource objects that are returned
|
||||
*/
|
||||
protected List<MapPropertySource> locateMapPropertySources(
|
||||
PropertySourceLocator propertySourceLocator, Environment environment) {
|
||||
|
||||
List<MapPropertySource> result = new ArrayList<>();
|
||||
PropertySource propertySource= propertySourceLocator.locate(environment);
|
||||
if(propertySource instanceof MapPropertySource) {
|
||||
result.add((MapPropertySource) propertySource);
|
||||
} else if(propertySource instanceof CompositePropertySource) {
|
||||
result.addAll(((CompositePropertySource) propertySource)
|
||||
.getPropertySources()
|
||||
.stream()
|
||||
.filter(p -> p instanceof MapPropertySource)
|
||||
.map(p -> (MapPropertySource) p)
|
||||
.collect(Collectors.toList()));
|
||||
} else {
|
||||
log.debug("Found property source that cannot be handled: "
|
||||
+ propertySource.getClass());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,22 +16,20 @@
|
||||
*/
|
||||
package org.springframework.cloud.kubernetes.config.reload;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.ConfigMap;
|
||||
import io.fabric8.kubernetes.api.model.Secret;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.kubernetes.client.KubernetesClientException;
|
||||
import io.fabric8.kubernetes.client.Watch;
|
||||
import io.fabric8.kubernetes.client.Watcher;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import org.springframework.cloud.kubernetes.config.ConfigMapPropertySource;
|
||||
import org.springframework.cloud.kubernetes.config.ConfigMapPropertySourceLocator;
|
||||
import org.springframework.cloud.kubernetes.config.SecretsPropertySource;
|
||||
import org.springframework.cloud.kubernetes.config.SecretsPropertySourceLocator;
|
||||
|
||||
import org.springframework.core.env.AbstractEnvironment;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
|
||||
@@ -127,14 +125,14 @@ public class EventBasedConfigurationChangeDetector extends ConfigurationChangeDe
|
||||
}
|
||||
|
||||
private void onEvent(ConfigMap configMap) {
|
||||
MapPropertySource currentConfigMapSource = findPropertySource(ConfigMapPropertySource.class);
|
||||
if (currentConfigMapSource != null) {
|
||||
MapPropertySource newConfigMapSource = configMapPropertySourceLocator.locate(environment);
|
||||
if (changed(currentConfigMapSource, newConfigMapSource)) {
|
||||
log.info("Detected change in config maps");
|
||||
reloadProperties();
|
||||
}
|
||||
}
|
||||
boolean changed = changed(
|
||||
locateMapPropertySources(configMapPropertySourceLocator, environment),
|
||||
findPropertySources(ConfigMapPropertySource.class)
|
||||
);
|
||||
if(changed) {
|
||||
log.info("Detected change in config maps");
|
||||
reloadProperties();
|
||||
}
|
||||
}
|
||||
|
||||
private void onEvent(Secret secret) {
|
||||
|
||||
@@ -16,14 +16,15 @@
|
||||
*/
|
||||
package org.springframework.cloud.kubernetes.config.reload;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import java.util.List;
|
||||
import javax.annotation.PostConstruct;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.cloud.kubernetes.config.ConfigMapPropertySource;
|
||||
import org.springframework.cloud.kubernetes.config.ConfigMapPropertySourceLocator;
|
||||
import org.springframework.cloud.kubernetes.config.SecretsPropertySource;
|
||||
import org.springframework.cloud.kubernetes.config.SecretsPropertySourceLocator;
|
||||
|
||||
import org.springframework.core.env.AbstractEnvironment;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
@@ -33,6 +34,8 @@ import org.springframework.scheduling.annotation.Scheduled;
|
||||
*/
|
||||
public class PollingConfigurationChangeDetector extends ConfigurationChangeDetector {
|
||||
|
||||
protected Log log = LogFactory.getLog(getClass());
|
||||
|
||||
private ConfigMapPropertySourceLocator configMapPropertySourceLocator;
|
||||
|
||||
private SecretsPropertySourceLocator secretsPropertySourceLocator;
|
||||
@@ -59,10 +62,14 @@ public class PollingConfigurationChangeDetector extends ConfigurationChangeDetec
|
||||
|
||||
boolean changedConfigMap = false;
|
||||
if (properties.isMonitoringConfigMaps()) {
|
||||
MapPropertySource currentConfigMapSource = findPropertySource(ConfigMapPropertySource.class);
|
||||
if (currentConfigMapSource != null) {
|
||||
MapPropertySource newConfigMapSource = configMapPropertySourceLocator.locate(environment);
|
||||
changedConfigMap = changed(currentConfigMapSource, newConfigMapSource);
|
||||
List<? extends MapPropertySource> currentConfigMapSources
|
||||
= findPropertySources(ConfigMapPropertySource.class);
|
||||
|
||||
if (!currentConfigMapSources.isEmpty()) {
|
||||
changedConfigMap = changed(
|
||||
locateMapPropertySources(configMapPropertySourceLocator, environment),
|
||||
currentConfigMapSources
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright (C) 2016 to the original 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
|
||||
*
|
||||
* http://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.kubernetes.config;
|
||||
|
||||
import static io.restassured.RestAssured.when;
|
||||
import static org.hamcrest.core.Is.is;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
|
||||
import io.fabric8.kubernetes.client.Config;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.kubernetes.client.server.mock.KubernetesServer;
|
||||
import io.restassured.RestAssured;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.kubernetes.config.example2.ExampleApp;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:cmoullia@redhat.com">Charles Moulliard</a>
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = ExampleApp.class,
|
||||
properties = {"spring.cloud.bootstrap.name=multiplecms"})
|
||||
public class MultipleConfigMapsSpringBootTest {
|
||||
|
||||
@ClassRule
|
||||
public static KubernetesServer server = new KubernetesServer();
|
||||
|
||||
private static KubernetesClient mockClient;
|
||||
|
||||
|
||||
@Value("${local.server.port}")
|
||||
private int port;
|
||||
|
||||
@BeforeClass
|
||||
public static void setUpBeforeClass() {
|
||||
mockClient = server.getClient();
|
||||
|
||||
//Configure the kubernetes master url to point to the mock server
|
||||
System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl());
|
||||
System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true");
|
||||
System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false");
|
||||
System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY, "false");
|
||||
System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, "test");
|
||||
|
||||
createConfigmap(
|
||||
server,
|
||||
"s1",
|
||||
"defnamespace",
|
||||
new HashMap<String, String>() {{
|
||||
put("bean.common-message","c1");
|
||||
put("bean.message1", "m1");
|
||||
}});
|
||||
|
||||
createConfigmap(
|
||||
server,
|
||||
"defname",
|
||||
"s2",
|
||||
new HashMap<String, String>() {{
|
||||
put("bean.common-message","c2");
|
||||
put("bean.message2", "m2");
|
||||
}});
|
||||
|
||||
createConfigmap(
|
||||
server,
|
||||
"othername",
|
||||
"othernamespace",
|
||||
new HashMap<String, String>() {{
|
||||
put("bean.common-message","c3");
|
||||
put("bean.message3", "m3");
|
||||
}});
|
||||
}
|
||||
|
||||
private static void createConfigmap(KubernetesServer server, String configMapName,
|
||||
String namespace, Map<String, String> data) {
|
||||
|
||||
server
|
||||
.expect()
|
||||
.withPath(String.format("/api/v1/namespaces/%s/configmaps/%s", namespace, configMapName))
|
||||
.andReturn(
|
||||
200,
|
||||
new ConfigMapBuilder()
|
||||
.withNewMetadata().withName(configMapName).endMetadata()
|
||||
.addToData(data)
|
||||
.build()
|
||||
)
|
||||
.always();
|
||||
}
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
RestAssured.baseURI = String.format("http://localhost:%d/", port);
|
||||
}
|
||||
|
||||
//the last confimap defined in 'multiplecms.yml' has the highest priority, so
|
||||
//the common property defined in all configmaps is taken from the last one defined
|
||||
@Test
|
||||
public void testCommonMessage() {
|
||||
assertResponse("/common", "c3");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMessage1() {
|
||||
assertResponse("/m1", "m1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMessage2() {
|
||||
assertResponse("/m2", "m2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMessage3() {
|
||||
assertResponse("/m3", "m3");
|
||||
}
|
||||
|
||||
private void assertResponse(String path, String expectedMessage) {
|
||||
when().get(path)
|
||||
.then()
|
||||
.statusCode(200)
|
||||
.body("message", is(expectedMessage));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package org.springframework.cloud.kubernetes.config.example2;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableConfigurationProperties(ExampleAppProps.class)
|
||||
public class ExampleApp {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(org.springframework.cloud.kubernetes.config.example.App.class, args);
|
||||
}
|
||||
|
||||
@RestController
|
||||
public static class Controller {
|
||||
|
||||
private final ExampleAppProps exampleAppProps;
|
||||
|
||||
public Controller(ExampleAppProps exampleAppProps) {
|
||||
this.exampleAppProps = exampleAppProps;
|
||||
}
|
||||
|
||||
@GetMapping("/common")
|
||||
public Response commonMessage() {
|
||||
return new Response(exampleAppProps.getCommonMessage());
|
||||
}
|
||||
|
||||
@GetMapping("/m1")
|
||||
public Response message1() {
|
||||
return new Response(exampleAppProps.getMessage1());
|
||||
}
|
||||
|
||||
@GetMapping("/m2")
|
||||
public Response message2() {
|
||||
return new Response(exampleAppProps.getMessage2());
|
||||
}
|
||||
|
||||
@GetMapping("/m3")
|
||||
public Response message3() {
|
||||
return new Response(exampleAppProps.getMessage3());
|
||||
}
|
||||
}
|
||||
|
||||
public static class Response {
|
||||
|
||||
private final String message;
|
||||
|
||||
public Response(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.springframework.cloud.kubernetes.config.example2;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@ConfigurationProperties("bean")
|
||||
public class ExampleAppProps {
|
||||
|
||||
private String commonMessage;
|
||||
private String message1;
|
||||
private String message2;
|
||||
private String message3;
|
||||
|
||||
public String getCommonMessage() {
|
||||
return commonMessage;
|
||||
}
|
||||
|
||||
public void setCommonMessage(String commonMessage) {
|
||||
this.commonMessage = commonMessage;
|
||||
}
|
||||
|
||||
public String getMessage1() {
|
||||
return message1;
|
||||
}
|
||||
|
||||
public void setMessage1(String message1) {
|
||||
this.message1 = message1;
|
||||
}
|
||||
|
||||
public String getMessage2() {
|
||||
return message2;
|
||||
}
|
||||
|
||||
public void setMessage2(String message2) {
|
||||
this.message2 = message2;
|
||||
}
|
||||
|
||||
public String getMessage3() {
|
||||
return message3;
|
||||
}
|
||||
|
||||
public void setMessage3(String message3) {
|
||||
this.message3 = message3;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
spring:
|
||||
application:
|
||||
name: name-in-file
|
||||
cloud:
|
||||
kubernetes:
|
||||
reload:
|
||||
enabled: false
|
||||
config:
|
||||
name: defname
|
||||
namespace: defnamespace
|
||||
sources:
|
||||
- name: s1
|
||||
- namespace: s2
|
||||
- name: othername
|
||||
namespace: othernamespace
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2016 to the original 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
|
||||
*
|
||||
* http://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.kubernetes.examples;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "dummy")
|
||||
public class DummyConfig {
|
||||
|
||||
private String message = "this is a dummy message";
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,11 +24,15 @@ import org.springframework.stereotype.Component;
|
||||
public class MyBean {
|
||||
|
||||
@Autowired
|
||||
private MyConfig config;
|
||||
private MyConfig myConfig;
|
||||
|
||||
@Autowired
|
||||
private DummyConfig dummyConfig;
|
||||
|
||||
@Scheduled(fixedDelay = 5000)
|
||||
public void hello() {
|
||||
System.out.println("The message is: " + config.getMessage());
|
||||
System.out.println("The first message is: " + myConfig.getMessage());
|
||||
System.out.println("The other message is: " + dummyConfig.getMessage());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
management:
|
||||
endpoint:
|
||||
restart:
|
||||
enabled: true
|
||||
health:
|
||||
enabled: true
|
||||
info:
|
||||
enabled: true
|
||||
@@ -0,0 +1,13 @@
|
||||
spring:
|
||||
application:
|
||||
name: reload-example
|
||||
cloud:
|
||||
kubernetes:
|
||||
reload:
|
||||
enabled: true
|
||||
mode: polling
|
||||
period: 5000
|
||||
config:
|
||||
sources:
|
||||
- name: other
|
||||
- name: ${spring.application.name}
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<include resource="org/springframework/boot/logging/logback/base.xml" />
|
||||
<logger name="org.hibernate.validator" level="info" /> <!-- Validator prints a lot of debug messages during integration tests -->
|
||||
<logger name="org.springframework.cloud.kubernetes" level="debug" />
|
||||
</configuration>
|
||||
Reference in New Issue
Block a user