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 <vivian@autonomic.ai>
This commit is contained in:
committed by
Ioannis Canellos
parent
01fe68eb82
commit
50ac65024b
@@ -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<String, Properties> 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();
|
||||
};
|
||||
|
||||
@@ -76,7 +76,7 @@ public class ConfigMapsSpringBootTest {
|
||||
System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, "test");
|
||||
|
||||
HashMap<String,String> 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<String,String> keys = (HashMap<String, String>) configmap.getData();
|
||||
assertEquals(keys.get("bean.message"),"Hello ConfigMap, %s!");
|
||||
assertEquals(keys.get("bean.greeting"),"Hello ConfigMap, %s!");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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!"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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!"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String,String> 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!"));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!"
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
bean:
|
||||
greeting: "Hello ConfigMap, %s!"
|
||||
farewell: "Goodbye ConfigMap, %s!"
|
||||
Reference in New Issue
Block a user