From a30b2d85dc7ea04add4d54a24ad04e0e787a4e59 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 21 May 2018 18:44:01 +0300 Subject: [PATCH] Allow the use of multiple configmaps as sources --- README.md | 30 +++- .../config/ConfigMapConfigProperties.java | 98 +++++++++++- .../ConfigMapPropertySourceLocator.java | 41 ++++- .../cloud/kubernetes/config/ConfigUtils.java | 15 +- .../config/SecretsPropertySource.java | 10 +- .../reload/ConfigurationChangeDetector.java | 51 +++++- ...EventBasedConfigurationChangeDetector.java | 26 ++-- .../PollingConfigurationChangeDetector.java | 21 ++- .../MultipleConfigMapsSpringBootTest.java | 147 ++++++++++++++++++ .../config/example2/ExampleApp.java | 60 +++++++ .../config/example2/ExampleAppProps.java | 44 ++++++ .../src/test/resources/multiplecms.yml | 16 ++ .../kubernetes/examples/DummyConfig.java | 36 +++++ .../cloud/kubernetes/examples/MyBean.java | 8 +- .../src/main/resources/application.yaml | 8 + .../src/main/resources/bootstrap.yaml | 13 ++ .../src/main/resources/logback.xml | 6 + ....properties => old-application.properties} | 0 18 files changed, 585 insertions(+), 45 deletions(-) create mode 100644 spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/MultipleConfigMapsSpringBootTest.java create mode 100644 spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/example2/ExampleApp.java create mode 100644 spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/example2/ExampleAppProps.java create mode 100644 spring-cloud-kubernetes-config/src/test/resources/multiplecms.yml create mode 100644 spring-cloud-kubernetes-examples/kubernetes-reload-example/src/main/java/org/springframework/cloud/kubernetes/examples/DummyConfig.java create mode 100644 spring-cloud-kubernetes-examples/kubernetes-reload-example/src/main/resources/application.yaml create mode 100644 spring-cloud-kubernetes-examples/kubernetes-reload-example/src/main/resources/bootstrap.yaml create mode 100644 spring-cloud-kubernetes-examples/kubernetes-reload-example/src/main/resources/logback.xml rename spring-cloud-kubernetes-examples/kubernetes-reload-example/src/main/resources/{application.properties => old-application.properties} (100%) diff --git a/README.md b/README.md index f24d8435..013abbfd 100644 --- a/README.md +++ b/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` diff --git a/spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/ConfigMapConfigProperties.java b/spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/ConfigMapConfigProperties.java index 3d85e4b3..63321392 100644 --- a/spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/ConfigMapConfigProperties.java +++ b/spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/ConfigMapConfigProperties.java @@ -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 paths = new LinkedList<>(); + private List sources = new LinkedList<>(); public boolean isEnableApi() { return enableApi; @@ -46,8 +49,101 @@ public class ConfigMapConfigProperties extends AbstractConfigProperties { return paths; } + public List getSources() { + return sources; + } + + public void setSources(List 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 determineSources() { + if (sources.isEmpty()) { + return new ArrayList() {{ + 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; + } + } } diff --git a/spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/ConfigMapPropertySourceLocator.java b/spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/ConfigMapPropertySourceLocator.java index 15104c9a..38c1e952 100644 --- a/spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/ConfigMapPropertySourceLocator.java +++ b/spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/ConfigMapPropertySourceLocator.java @@ -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 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 + ); + } } diff --git a/spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/ConfigUtils.java b/spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/ConfigUtils.java index 4673186b..1271b537 100644 --- a/spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/ConfigUtils.java +++ b/spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/ConfigUtils.java @@ -14,12 +14,13 @@ public class ConfigUtils { private static final Log LOG = LogFactory.getLog(ConfigUtils.class); - public static String getApplicationName(Environment env, C config) { - String name = config.getName(); + public static 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 String getApplicationNamespace(KubernetesClient client, Environment env, C config) { - String namespace = config.getNamespace(); + public static 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(); diff --git a/spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/SecretsPropertySource.java b/spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/SecretsPropertySource.java index 9299f136..73ea52c8 100644 --- a/spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/SecretsPropertySource.java +++ b/spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/SecretsPropertySource.java @@ -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 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 result = new HashMap<>(); if (config.isEnableApi()) { diff --git a/spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/reload/ConfigurationChangeDetector.java b/spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/reload/ConfigurationChangeDetector.java index 1f05678c..fa55b9bc 100644 --- a/spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/reload/ConfigurationChangeDetector.java +++ b/spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/reload/ConfigurationChangeDetector.java @@ -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 l1, + List 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 locateMapPropertySources( + PropertySourceLocator propertySourceLocator, Environment environment) { + + List 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; + } + } diff --git a/spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/reload/EventBasedConfigurationChangeDetector.java b/spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/reload/EventBasedConfigurationChangeDetector.java index 4f0dc34e..431dbe61 100644 --- a/spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/reload/EventBasedConfigurationChangeDetector.java +++ b/spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/reload/EventBasedConfigurationChangeDetector.java @@ -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) { diff --git a/spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/reload/PollingConfigurationChangeDetector.java b/spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/reload/PollingConfigurationChangeDetector.java index ee3db51a..27e13032 100644 --- a/spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/reload/PollingConfigurationChangeDetector.java +++ b/spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/reload/PollingConfigurationChangeDetector.java @@ -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 currentConfigMapSources + = findPropertySources(ConfigMapPropertySource.class); + + if (!currentConfigMapSources.isEmpty()) { + changedConfigMap = changed( + locateMapPropertySources(configMapPropertySourceLocator, environment), + currentConfigMapSources + ); } } diff --git a/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/MultipleConfigMapsSpringBootTest.java b/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/MultipleConfigMapsSpringBootTest.java new file mode 100644 index 00000000..16d4185d --- /dev/null +++ b/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/MultipleConfigMapsSpringBootTest.java @@ -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 Charles Moulliard + */ +@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() {{ + put("bean.common-message","c1"); + put("bean.message1", "m1"); + }}); + + createConfigmap( + server, + "defname", + "s2", + new HashMap() {{ + put("bean.common-message","c2"); + put("bean.message2", "m2"); + }}); + + createConfigmap( + server, + "othername", + "othernamespace", + new HashMap() {{ + put("bean.common-message","c3"); + put("bean.message3", "m3"); + }}); + } + + private static void createConfigmap(KubernetesServer server, String configMapName, + String namespace, Map 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)); + } + +} diff --git a/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/example2/ExampleApp.java b/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/example2/ExampleApp.java new file mode 100644 index 00000000..df77a4ba --- /dev/null +++ b/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/example2/ExampleApp.java @@ -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; + } + } + +} diff --git a/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/example2/ExampleAppProps.java b/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/example2/ExampleAppProps.java new file mode 100644 index 00000000..f6630b3e --- /dev/null +++ b/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/example2/ExampleAppProps.java @@ -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; + } +} diff --git a/spring-cloud-kubernetes-config/src/test/resources/multiplecms.yml b/spring-cloud-kubernetes-config/src/test/resources/multiplecms.yml new file mode 100644 index 00000000..bf938dd6 --- /dev/null +++ b/spring-cloud-kubernetes-config/src/test/resources/multiplecms.yml @@ -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 diff --git a/spring-cloud-kubernetes-examples/kubernetes-reload-example/src/main/java/org/springframework/cloud/kubernetes/examples/DummyConfig.java b/spring-cloud-kubernetes-examples/kubernetes-reload-example/src/main/java/org/springframework/cloud/kubernetes/examples/DummyConfig.java new file mode 100644 index 00000000..2f890410 --- /dev/null +++ b/spring-cloud-kubernetes-examples/kubernetes-reload-example/src/main/java/org/springframework/cloud/kubernetes/examples/DummyConfig.java @@ -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; + } + +} diff --git a/spring-cloud-kubernetes-examples/kubernetes-reload-example/src/main/java/org/springframework/cloud/kubernetes/examples/MyBean.java b/spring-cloud-kubernetes-examples/kubernetes-reload-example/src/main/java/org/springframework/cloud/kubernetes/examples/MyBean.java index e13ca7be..ee60deb1 100644 --- a/spring-cloud-kubernetes-examples/kubernetes-reload-example/src/main/java/org/springframework/cloud/kubernetes/examples/MyBean.java +++ b/spring-cloud-kubernetes-examples/kubernetes-reload-example/src/main/java/org/springframework/cloud/kubernetes/examples/MyBean.java @@ -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()); } diff --git a/spring-cloud-kubernetes-examples/kubernetes-reload-example/src/main/resources/application.yaml b/spring-cloud-kubernetes-examples/kubernetes-reload-example/src/main/resources/application.yaml new file mode 100644 index 00000000..9e359122 --- /dev/null +++ b/spring-cloud-kubernetes-examples/kubernetes-reload-example/src/main/resources/application.yaml @@ -0,0 +1,8 @@ +management: + endpoint: + restart: + enabled: true + health: + enabled: true + info: + enabled: true diff --git a/spring-cloud-kubernetes-examples/kubernetes-reload-example/src/main/resources/bootstrap.yaml b/spring-cloud-kubernetes-examples/kubernetes-reload-example/src/main/resources/bootstrap.yaml new file mode 100644 index 00000000..3cd101e3 --- /dev/null +++ b/spring-cloud-kubernetes-examples/kubernetes-reload-example/src/main/resources/bootstrap.yaml @@ -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} diff --git a/spring-cloud-kubernetes-examples/kubernetes-reload-example/src/main/resources/logback.xml b/spring-cloud-kubernetes-examples/kubernetes-reload-example/src/main/resources/logback.xml new file mode 100644 index 00000000..5fa4e753 --- /dev/null +++ b/spring-cloud-kubernetes-examples/kubernetes-reload-example/src/main/resources/logback.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/spring-cloud-kubernetes-examples/kubernetes-reload-example/src/main/resources/application.properties b/spring-cloud-kubernetes-examples/kubernetes-reload-example/src/main/resources/old-application.properties similarity index 100% rename from spring-cloud-kubernetes-examples/kubernetes-reload-example/src/main/resources/application.properties rename to spring-cloud-kubernetes-examples/kubernetes-reload-example/src/main/resources/old-application.properties