From 296bf1e351c24107c308422f8e5f24c0cdde1eba Mon Sep 17 00:00:00 2001 From: Ali Shahbour Date: Wed, 19 Jun 2019 17:57:39 +0300 Subject: [PATCH] Search configMap with active profile name. (#343) * Search configMap with active profile name. * Fix checkstyle errors * fix documentation * fix documentation * Update document per latest update to property-source-config * fix @author --- .../main/asciidoc/property-source-config.adoc | 53 +++++++++ .../config/ConfigMapPropertySource.java | 26 +++- ...sWithActiveProfilesNameSpringBootTest.java | 111 ++++++++++++++++++ ...application-with-active-profiles-name.yaml | 4 + 4 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/ConfigMapsWithActiveProfilesNameSpringBootTest.java create mode 100644 spring-cloud-kubernetes-config/src/test/resources/application-with-active-profiles-name.yaml diff --git a/docs/src/main/asciidoc/property-source-config.adoc b/docs/src/main/asciidoc/property-source-config.adoc index 1b81357f..2ec1adf9 100644 --- a/docs/src/main/asciidoc/property-source-config.adoc +++ b/docs/src/main/asciidoc/property-source-config.adoc @@ -182,6 +182,59 @@ However, if the `production` profile is active, the configuration becomes: If both profiles are active, the property that appears last within the `ConfigMap` overwrites any preceding values. +Another option is to create a different config map per profile and spring boot will automatically fetch it based +on active profiles + +==== +[source,yaml] +---- +kind: ConfigMap +apiVersion: v1 +metadata: + name: demo +data: + application.yml: |- + greeting: + message: Say Hello to the World + farewell: + message: Say Goodbye +---- +==== +==== +[source,yaml] +---- +kind: ConfigMap +apiVersion: v1 +metadata: + name: demo-development +data: + application.yml: |- + spring: + profiles: development + greeting: + message: Say Hello to the Developers + farewell: + message: Say Goodbye to the Developers +---- +==== +==== +[source,yaml] +---- +kind: ConfigMap +apiVersion: v1 +metadata: + name: demo-production +data: + application.yml: |- + spring: + profiles: production + greeting: + message: Say Hello to the Ops + farewell: + message: Say Goodbye +---- +==== + To tell Spring Boot which `profile` should be enabled at bootstrap, you can pass a system property to the Java command. To do so, you can launch your Spring Boot application with an environment variable that you can define with the OpenShift diff --git a/spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/ConfigMapPropertySource.java b/spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/ConfigMapPropertySource.java index 10295ece..a74ea030 100644 --- a/spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/ConfigMapPropertySource.java +++ b/spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/ConfigMapPropertySource.java @@ -40,6 +40,7 @@ import static org.springframework.cloud.kubernetes.config.PropertySourceUtils.ya * A {@link MapPropertySource} that uses Kubernetes config maps. * * @author Ioannis Canellos + * @author Ali Shahbour */ public class ConfigMapPropertySource extends MapPropertySource { @@ -59,7 +60,7 @@ public class ConfigMapPropertySource extends MapPropertySource { public ConfigMapPropertySource(KubernetesClient client, String name, String namespace, String[] profiles) { - this(client, name, null, createEnvironmentWithActiveProfiles(profiles)); + this(client, name, namespace, createEnvironmentWithActiveProfiles(profiles)); } private static Environment createEnvironmentWithActiveProfiles( @@ -88,13 +89,33 @@ public class ConfigMapPropertySource extends MapPropertySource { private static Map getData(KubernetesClient client, String name, String namespace, Environment environment) { try { + Map result = new HashMap<>(); ConfigMap map = StringUtils.isEmpty(namespace) ? client.configMaps().withName(name).get() : client.configMaps().inNamespace(namespace).withName(name).get(); if (map != null) { - return processAllEntries(map.getData(), environment); + result.putAll(processAllEntries(map.getData(), environment)); } + + if (environment != null) { + for (String activeProfile:environment.getActiveProfiles()) { + + String mapNameWithProfile = name + "-" + activeProfile; + + ConfigMap mapWithProfile = StringUtils.isEmpty(namespace) + ? client.configMaps().withName(mapNameWithProfile).get() + : client.configMaps().inNamespace(namespace).withName(mapNameWithProfile).get(); + + if (mapWithProfile != null) { + result.putAll(processAllEntries(mapWithProfile.getData(), environment)); + } + + } + } + + return result; + } catch (Exception e) { LOG.warn("Can't read configMap with name: [" + name + "] in namespace:[" @@ -104,6 +125,7 @@ public class ConfigMapPropertySource extends MapPropertySource { return new HashMap<>(); } + private static Map processAllEntries(Map input, Environment environment) { diff --git a/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/ConfigMapsWithActiveProfilesNameSpringBootTest.java b/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/ConfigMapsWithActiveProfilesNameSpringBootTest.java new file mode 100644 index 00000000..e231efd6 --- /dev/null +++ b/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/ConfigMapsWithActiveProfilesNameSpringBootTest.java @@ -0,0 +1,111 @@ +/* + * 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 + * + * 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 java.util.HashMap; + +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 org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.config.example.App; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.reactive.server.WebTestClient; + +import static org.springframework.cloud.kubernetes.config.ConfigMapTestUtil.readResourceFile; + +/** + * @author Ali Shahbour + */ +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, properties = { + "spring.application.name=configmap-with-active-profile-name-example", + "spring.cloud.kubernetes.reload.enabled=false" }) +@ActiveProfiles("development") +@AutoConfigureWebTestClient +public class ConfigMapsWithActiveProfilesNameSpringBootTest { + + private static final String APPLICATION_NAME = "configmap-with-active-profile-name-example"; + + @ClassRule + public static KubernetesServer server = new KubernetesServer(); + + private static KubernetesClient mockClient; + + @Autowired(required = false) + Config config; + + @Autowired + private WebTestClient webClient; + + @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"); + + HashMap data = new HashMap<>(); + data.put("application.yml", readResourceFile("application-with-profiles.yaml")); + server.expect().withPath("/api/v1/namespaces/test/configmaps/" + APPLICATION_NAME) + .andReturn(200, new ConfigMapBuilder().withNewMetadata() + .withName(APPLICATION_NAME).endMetadata().addToData(data).build()) + .always(); + + HashMap dataWithName = new HashMap<>(); + dataWithName.put("application.yml", + readResourceFile("application-with-active-profiles-name.yaml")); + server.expect() + .withPath("/api/v1/namespaces/test/configmaps/" + APPLICATION_NAME + + "-development") + .andReturn(200, + new ConfigMapBuilder().withNewMetadata() + .withName(APPLICATION_NAME + "-development").endMetadata() + .addToData(dataWithName).build()) + .always(); + } + + @Test + public void testGreetingEndpoint() { + this.webClient.get().uri("/api/greeting").exchange().expectStatus().isOk() + .expectBody().jsonPath("content") + .isEqualTo("Hello ConfigMap Active Profile Name, World!"); + } + + @Test + public void testFarewellEndpoint() { + this.webClient.get().uri("/api/farewell").exchange().expectStatus().isOk() + .expectBody().jsonPath("content") + .isEqualTo("Goodbye ConfigMap default, World!"); + } + +} diff --git a/spring-cloud-kubernetes-config/src/test/resources/application-with-active-profiles-name.yaml b/spring-cloud-kubernetes-config/src/test/resources/application-with-active-profiles-name.yaml new file mode 100644 index 00000000..98357fa6 --- /dev/null +++ b/spring-cloud-kubernetes-config/src/test/resources/application-with-active-profiles-name.yaml @@ -0,0 +1,4 @@ +spring: + profiles: development +bean: + greeting: "Hello ConfigMap Active Profile Name, %s!"