From 50ac65024b15f991b3e42e707e36e7187d4f9ace Mon Sep 17 00:00:00 2001 From: Rafay Khan Date: Wed, 20 Jun 2018 17:28:47 -0400 Subject: [PATCH] Rework profile-based app.yaml parsing to support yaml merging With the current implementation, any default properties (i.e. part of yaml docs that did not specify a "spring.profile" entry) would not be read. This caused unexpected behavior where "default" keys were not being read, and if NO document specified a "spring.profile" entry, the configmap would be completely ignored. This new logic instead performs proper yaml merges between any yaml doc that has no "spring.profile" entry, as well as docs with the specific "profile" we have active Co-authored-by: Vivian Li --- .../config/ConfigMapPropertySource.java | 16 ++-- .../config/ConfigMapsSpringBootTest.java | 4 +- ...ProfilesNoActiveProfileSpringBootTest.java | 18 ++-- .../ConfigMapsWithProfilesSpringBootTest.java | 14 ++-- ...nfigMapsWithoutProfilesSpringBootTest.java | 83 +++++++++++++++++++ .../config/example/GreetingController.java | 8 +- .../config/example/GreetingProperties.java | 18 ++-- .../resources/application-with-profiles.yaml | 7 +- .../application-without-profiles.yaml | 3 + 9 files changed, 142 insertions(+), 29 deletions(-) create mode 100644 spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/ConfigMapsWithoutProfilesSpringBootTest.java create mode 100644 spring-cloud-kubernetes-config/src/test/resources/application-without-profiles.yaml 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 45069221..e7265052 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 @@ -18,6 +18,7 @@ package org.springframework.cloud.kubernetes.config; import static java.util.Arrays.asList; +import static org.springframework.beans.factory.config.YamlProcessor.MatchStatus.ABSTAIN; import static org.springframework.beans.factory.config.YamlProcessor.MatchStatus.FOUND; import static org.springframework.beans.factory.config.YamlProcessor.MatchStatus.NOT_FOUND; @@ -122,13 +123,14 @@ public class ConfigMapPropertySource extends KubernetesPropertySource { private static Function yamlParserGenerator(final String[] profiles) { return s -> { YamlPropertiesFactoryBean yamlFactory = new YamlPropertiesFactoryBean(); - if ((profiles != null) && (profiles.length > 0)){ - yamlFactory.setDocumentMatchers( - (DocumentMatcher) properties -> - (asList(profiles).contains(properties.getProperty("spring.profiles")) ? - FOUND : NOT_FOUND) - ); - } + yamlFactory.setDocumentMatchers(properties -> { + String profileProperty = properties.getProperty("spring.profiles"); + if (profileProperty != null && profileProperty.length() > 0) { + return asList(profiles).contains(profileProperty) ? FOUND : NOT_FOUND; + } else { + return ABSTAIN; + } + }); yamlFactory.setResources(new ByteArrayResource(s.getBytes())); return yamlFactory.getObject(); }; diff --git a/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/ConfigMapsSpringBootTest.java b/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/ConfigMapsSpringBootTest.java index 21398d55..4d890e8d 100644 --- a/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/ConfigMapsSpringBootTest.java +++ b/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/ConfigMapsSpringBootTest.java @@ -76,7 +76,7 @@ public class ConfigMapsSpringBootTest { System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, "test"); HashMap data = new HashMap<>(); - data.put("bean.message","Hello ConfigMap, %s!"); + data.put("bean.greeting","Hello ConfigMap, %s!"); server.expect().withPath("/api/v1/namespaces/test/configmaps/" + APPLICATION_NAME).andReturn(200, new ConfigMapBuilder() .withNewMetadata().withName(APPLICATION_NAME).endMetadata() .addToData(data) @@ -108,7 +108,7 @@ public class ConfigMapsSpringBootTest { public void testConfigMap() { ConfigMap configmap = mockClient.configMaps().inNamespace("test").withName(APPLICATION_NAME).get(); HashMap keys = (HashMap) configmap.getData(); - assertEquals(keys.get("bean.message"),"Hello ConfigMap, %s!"); + assertEquals(keys.get("bean.greeting"),"Hello ConfigMap, %s!"); } } diff --git a/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/ConfigMapsWithProfilesNoActiveProfileSpringBootTest.java b/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/ConfigMapsWithProfilesNoActiveProfileSpringBootTest.java index f7b0efa7..abdade9b 100644 --- a/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/ConfigMapsWithProfilesNoActiveProfileSpringBootTest.java +++ b/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/ConfigMapsWithProfilesNoActiveProfileSpringBootTest.java @@ -36,6 +36,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; 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; /** @@ -82,17 +83,22 @@ public class ConfigMapsWithProfilesNoActiveProfileSpringBootTest { .always(); } - @Before - public void setUp() { - RestAssured.baseURI = String.format("http://localhost:%d/api/greeting", port); - } - @Test public void testGreetingEndpoint() { + RestAssured.baseURI = String.format("http://localhost:%d/api/greeting", port); when().get() .then() .statusCode(200) - .body("content", is("Hello ConfigMap prod, World!")); + .body("content", is("Hello ConfigMap default, World!")); + } + + @Test + public void testFarewellEndpoint() { + RestAssured.baseURI = String.format("http://localhost:%d/api/farewell", port); + when().get() + .then() + .statusCode(200) + .body("content", is("Goodbye ConfigMap default, World!")); } } diff --git a/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/ConfigMapsWithProfilesSpringBootTest.java b/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/ConfigMapsWithProfilesSpringBootTest.java index 64dbd26c..4bb5bdad 100644 --- a/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/ConfigMapsWithProfilesSpringBootTest.java +++ b/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/ConfigMapsWithProfilesSpringBootTest.java @@ -87,17 +87,21 @@ public class ConfigMapsWithProfilesSpringBootTest { .always(); } - @Before - public void setUp() { - RestAssured.baseURI = String.format("http://localhost:%d/api/greeting", port); - } - @Test public void testGreetingEndpoint() { + RestAssured.baseURI = String.format("http://localhost:%d/api/greeting", port); when().get() .then() .statusCode(200) .body("content", is("Hello ConfigMap dev, World!")); } + @Test + public void testFarewellEndpoint() { + RestAssured.baseURI = String.format("http://localhost:%d/api/farewell", port); + when().get() + .then() + .statusCode(200) + .body("content", is("Goodbye ConfigMap default, World!")); + } } diff --git a/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/ConfigMapsWithoutProfilesSpringBootTest.java b/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/ConfigMapsWithoutProfilesSpringBootTest.java new file mode 100644 index 00000000..7a981f14 --- /dev/null +++ b/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/ConfigMapsWithoutProfilesSpringBootTest.java @@ -0,0 +1,83 @@ +package org.springframework.cloud.kubernetes.config; + +import static io.restassured.RestAssured.when; +import static org.hamcrest.core.Is.is; +import static org.springframework.cloud.kubernetes.config.ConfigMapTestUtil.readResourceFile; + +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 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.beans.factory.annotation.Value; +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; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = App.class, + properties = { "spring.application.name=configmap-without-profile-example", + "spring.cloud.kubernetes.reload.enabled=false"} +) +@ActiveProfiles("development") +public class ConfigMapsWithoutProfilesSpringBootTest { + + @ClassRule + public static KubernetesServer server = new KubernetesServer(); + + private static KubernetesClient mockClient; + + @Autowired(required = false) + Config config; + + private static final String APPLICATION_NAME = "configmap-without-profile-example"; + + @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"); + + HashMap data = new HashMap<>(); + data.put("application.yml", readResourceFile("application-without-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(); + } + + @Test + public void testGreetingEndpoint() { + RestAssured.baseURI = String.format("http://localhost:%d/api/greeting", port); + when().get() + .then() + .statusCode(200) + .body("content", is("Hello ConfigMap, World!")); + } + + @Test + public void testFarewellEndpoint() { + RestAssured.baseURI = String.format("http://localhost:%d/api/farewell", port); + when().get() + .then() + .statusCode(200) + .body("content", is("Goodbye ConfigMap, World!")); + } +} diff --git a/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/example/GreetingController.java b/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/example/GreetingController.java index 8c771908..47e9ea0e 100644 --- a/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/example/GreetingController.java +++ b/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/example/GreetingController.java @@ -37,7 +37,13 @@ public class GreetingController { @RequestMapping("/api/greeting") public Greeting greeting(@RequestParam(value="name", defaultValue="World") String name) { - String message = String.format(properties.getMessage(), name); + String message = String.format(properties.getGreeting(), name); + return new Greeting(message); + } + + @RequestMapping("/api/farewell") + public Greeting farewell(@RequestParam(value="name", defaultValue="World") String name) { + String message = String.format(properties.getFarewell(), name); return new Greeting(message); } } diff --git a/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/example/GreetingProperties.java b/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/example/GreetingProperties.java index c0222488..a5f72acb 100644 --- a/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/example/GreetingProperties.java +++ b/spring-cloud-kubernetes-config/src/test/java/org/springframework/cloud/kubernetes/config/example/GreetingProperties.java @@ -24,14 +24,22 @@ import org.springframework.context.annotation.Configuration; @ConfigurationProperties(prefix = "bean") public class GreetingProperties { - private String message = "Hello, %s!"; + private String greeting = "Hello, %s!"; + private String farewell = "Goodbye, %s!"; - public String getMessage() { - return message; + public String getGreeting() { + return greeting; } - public void setMessage(String message) { - this.message = message; + public void setGreeting(String greeting) { + this.greeting = greeting; } + public String getFarewell() { + return farewell; + } + + public void setFarewell(String farewell) { + this.farewell = farewell; + } } diff --git a/spring-cloud-kubernetes-config/src/test/resources/application-with-profiles.yaml b/spring-cloud-kubernetes-config/src/test/resources/application-with-profiles.yaml index ea00f59a..35457f90 100644 --- a/spring-cloud-kubernetes-config/src/test/resources/application-with-profiles.yaml +++ b/spring-cloud-kubernetes-config/src/test/resources/application-with-profiles.yaml @@ -1,12 +1,13 @@ bean: - message: "Hello ConfigMap default, %s!" + greeting: "Hello ConfigMap default, %s!" + farewell: "Goodbye ConfigMap default, %s!" --- spring: profiles: development bean: - message: "Hello ConfigMap dev, %s!" + greeting: "Hello ConfigMap dev, %s!" --- spring: profiles: production bean: - message: "Hello ConfigMap prod, %s!" + greeting: "Hello ConfigMap prod, %s!" diff --git a/spring-cloud-kubernetes-config/src/test/resources/application-without-profiles.yaml b/spring-cloud-kubernetes-config/src/test/resources/application-without-profiles.yaml new file mode 100644 index 00000000..06f5c0e1 --- /dev/null +++ b/spring-cloud-kubernetes-config/src/test/resources/application-without-profiles.yaml @@ -0,0 +1,3 @@ +bean: + greeting: "Hello ConfigMap, %s!" + farewell: "Goodbye ConfigMap, %s!"