* moved code + tests added

* started

* checkstyle

* trigger build one more time

* fabric8 changes + tests

* checkstyle

* checkstyle

* fix same mapping controller entries

* started work on k8s-client

* prepare test appliations

* minor rename

* change to be able to read easier

* k8s client fix + tests

* documentation

* trigger build

* more changes

* rename property and everything related to it

* rename property and everything related to it
This commit is contained in:
erabii
2021-10-21 15:12:35 -04:00
committed by GitHub
parent 32a8ae83dd
commit 8e51305da0
40 changed files with 1512 additions and 59 deletions

View File

@@ -353,7 +353,7 @@ Notice that `spring.cloud.kubernetes.config.useNameAsPrefix` has a _lower_ prior
This allows you to set a "default" strategy for all sources, at the same time allowing to override only a few.
If using the config map name is not an option, you can specify a different strategy, called : `explicitPrefix`. Since this is an _explicit_ prefix that
you select, it can only be supplied to the `sources` level. At the same time it has a higher priority than `useNameASPrefix`. Let's suppose we have a third config map with these entries:
you select, it can only be supplied to the `sources` level. At the same time it has a higher priority than `useNameAsPrefix`. Let's suppose we have a third config map with these entries:
====
@@ -400,6 +400,50 @@ will result in three properties being generated:
- `config-map-three.greetings.message` equal to `Say Hello from three`.
By default, besides reading the config map that is specified in the `sources` configuration, Spring will also try to read
all properties from "profile aware" sources. The easiest way to explain this is via an example. Let's suppose your application
enables a profile called "dev" and you have a configuration like the one below:
====
[source,yaml]
----
spring:
application:
name: spring-k8s
cloud:
kubernetes:
config:
namespace: default-namespace
sources:
- name: config-map-one
----
====
Besides reading the `config-map-one`, Spring will also try to read `config-map-one-dev`; in this particular order. Each active profile
generates such a profile aware config map.
Though your application should not be impacted by such a config map, it can be disabled if needed:
====
[source,yaml]
----
spring:
application:
name: spring-k8s
cloud:
kubernetes:
config:
includeProfileSpecificSources: false
namespace: default-namespace
sources:
- name: config-map-one
includeProfileSpecificSources: false
----
====
Notice that just like before, there are two levels where you can specify this property: for all config maps or
for individual ones; the latter having a higher priority.
NOTE: You should check the security configuration section. To access config maps from inside a pod you need to have the correct
Kubernetes service accounts, roles and role bindings.

View File

@@ -68,6 +68,7 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-test-support</artifactId>
@@ -86,6 +87,18 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

View File

@@ -39,18 +39,25 @@ public class KubernetesClientConfigMapPropertySource extends ConfigMapPropertySo
private static final Log LOG = LogFactory.getLog(KubernetesClientConfigMapPropertySource.class);
@Deprecated
public KubernetesClientConfigMapPropertySource(CoreV1Api coreV1Api, String name, String namespace,
Environment environment, String prefix) {
super(getName(name, namespace), getData(coreV1Api, name, namespace, environment, prefix));
Environment environment) {
super(getName(name, namespace), getData(coreV1Api, name, namespace, environment, "", true));
}
public KubernetesClientConfigMapPropertySource(CoreV1Api coreV1Api, String name, String namespace,
Environment environment, String prefix, boolean includeProfileSpecificSources) {
super(getName(name, namespace),
getData(coreV1Api, name, namespace, environment, prefix, includeProfileSpecificSources));
}
private static Map<String, Object> getData(CoreV1Api coreV1Api, String name, String namespace,
Environment environment, String prefix) {
Environment environment, String prefix, boolean includeProfileSpecificSources) {
try {
Set<String> names = new HashSet<>();
names.add(name);
if (environment != null) {
if (environment != null && includeProfileSpecificSources) {
for (String activeProfile : environment.getActiveProfiles()) {
names.add(name + "-" + activeProfile);
}

View File

@@ -88,7 +88,7 @@ public class KubernetesClientConfigMapPropertySourceLocator extends ConfigMapPro
}
return new KubernetesClientConfigMapPropertySource(coreV1Api, name, namespace, environment,
normalizedSource.getPrefix());
normalizedSource.getPrefix(), normalizedSource.isIncludeProfileSpecificSources());
}
}

View File

@@ -0,0 +1,118 @@
/*
* Copyright 2013-2020 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
*
* https://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.client.config;
import com.github.tomakehurst.wiremock.client.WireMock;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
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.client.config.applications.include_profile_specific_sources.IncludeProfileSpecificSourcesApp;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.reactive.server.WebTestClient;
/**
* The stub data for this test is in : IncludeProfileSpecificSourcesConfigurationStub
*
* @author wind57
*/
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = IncludeProfileSpecificSourcesApp.class,
properties = { "spring.cloud.bootstrap.name=include-profile-specific-sources", "include.profile.specific.sources=true" })
@AutoConfigureWebTestClient
@ActiveProfiles("dev")
class KubernetesClientConfigMapIncludeProfileSpecificSourcesTests {
@Autowired
private WebTestClient webClient;
@AfterEach
public void afterEach() {
WireMock.reset();
}
@AfterAll
public static void afterAll() {
WireMock.shutdownServer();
}
/**
* <pre>
* 'spring.cloud.kubernetes.config.includeProfileSpecificSources=false'
* 'spring.cloud.kubernetes.config.sources[0].includeProfileSpecificSources=true'
* 'spring.cloud.kubernetes.config.sources[0].name=config-map-one'
*
* We do not define config-map 'config-map-one', but we do define 'config-map-one-dev'.
*
* As such: @ConfigurationProperties("one") must be resolved from 'config-map-one-dev'
* </pre>
*/
@Test
public void testOne() {
this.webClient.get().uri("/profile-specific/one").exchange().expectStatus().isOk().expectBody(String.class)
.value(Matchers.equalTo("one"));
}
/**
* <pre>
* 'spring.cloud.kubernetes.config.includeProfileSpecificSources=false'
* 'spring.cloud.kubernetes.config.sources[1].includeProfileSpecificSources=false'
* 'spring.cloud.kubernetes.config.sources[1].name=config-map-two'
*
* We define config-map 'config-map-two', but we also define 'config-map-two-dev'.
* This tests proves that data will be read from 'config-map-two' _only_, even if 'config-map-two-dev'
* also exists. This happens because of the 'includeProfileSpecificSources=false' property defined at the source level.
* If this would be incorrect, the value we read from '/profile-specific/two' would have been 'twoDev' and _not_ 'two',
* simply because 'config-map-two-dev' would override the property value.
*
* As such: @ConfigurationProperties("two") must be resolved from 'config-map-two'
* </pre>
*/
@Test
public void testTwo() {
this.webClient.get().uri("/profile-specific/two").exchange().expectStatus().isOk().expectBody(String.class)
.value(Matchers.equalTo("two"));
}
/**
* <pre>
* 'spring.cloud.kubernetes.config.includeProfileSpecificSources=false'
* 'spring.cloud.kubernetes.config.sources[2].name=config-map-three'
*
* We define config-map 'config-map-three', but we also define 'config-map-three-dev'.
* This tests proves that data will be read from 'config-map-three' _only_, even if 'config-map-three-dev'
* also exists. This happens because the 'includeProfileSpecificSources' property is not defined at the source level,
* but it is defaulted from the root level, where we set it to false.
* If this would be incorrect, the value we read from '/profile-specific/three' would have been 'threeDev' and _not_ 'three',
* simply because 'config-map-three-dev' would override the property value.
*
* As such: @ConfigurationProperties("three") must be resolved from 'config-map-three'
* </pre>
*/
@Test
public void testThree() {
this.webClient.get().uri("/profile-specific/three").exchange().expectStatus().isOk().expectBody(String.class)
.value(Matchers.equalTo("three"));
}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright 2013-2020 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
*
* https://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.client.config;
import com.github.tomakehurst.wiremock.client.WireMock;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
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.client.config.applications.config_map_name_as_prefix.WithPrefixApp;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.reactive.server.WebTestClient;
/**
* The stub data for this test is in : ConfigMapNameAsPrefixConfigurationStub
*
* @author wind57
*/
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = WithPrefixApp.class,
properties = { "spring.cloud.bootstrap.name=config-map-name-as-prefix", "config.map.name.as.prefix.stub=true" })
@AutoConfigureWebTestClient
public class KubernetesClientConfigMapNameAsPrefixTests {
@Autowired
private WebTestClient webClient;
@AfterEach
public void afterEach() {
WireMock.reset();
}
@AfterAll
public static void afterAll() {
WireMock.shutdownServer();
}
/**
* <pre>
* 'spring.cloud.kubernetes.config.useNameAsPrefix=true'
* 'spring.cloud.kubernetes.config.sources[0].useNameAsPrefix=false'
* ("one.property", "one")
*
* As such: @ConfigurationProperties("one")
* </pre>
*/
@Test
public void testOne() {
this.webClient.get().uri("/prefix/one").exchange().expectStatus().isOk().expectBody(String.class)
.value(Matchers.equalTo("one"));
}
/**
* <pre>
* 'spring.cloud.kubernetes.config.useNameAsPrefix=true'
* 'spring.cloud.kubernetes.config.sources[1].explicitPrefix=two'
* ("property", "two")
*
* As such: @ConfigurationProperties("two")
* </pre>
*/
@Test
public void testTwo() {
this.webClient.get().uri("/prefix/two").exchange().expectStatus().isOk().expectBody(String.class)
.value(Matchers.equalTo("two"));
}
/**
* <pre>
* 'spring.cloud.kubernetes.config.useNameAsPrefix=true'
* 'spring.cloud.kubernetes.config.sources[2].name=config-map-three'
* ("property", "three")
*
* As such: @ConfigurationProperties(prefix = "config-map-three")
* </pre>
*/
@Test
public void testThree() {
this.webClient.get().uri("/prefix/three").exchange().expectStatus().isOk().expectBody(String.class)
.value(Matchers.equalTo("three"));
}
}

View File

@@ -63,8 +63,6 @@ class KubernetesClientConfigMapPropertySourceLocatorTests {
+ "logging.level.org.springframework.cloud.kubernetes=TRACE")
.build());
private static final String API = "/api/v1/namespaces/default/configmaps";
private static WireMockServer wireMockServer;
@BeforeAll
@@ -92,7 +90,7 @@ class KubernetesClientConfigMapPropertySourceLocatorTests {
@Test
void locateWithoutSources() {
CoreV1Api api = new CoreV1Api();
stubFor(get(API)
stubFor(get("/api/v1/namespaces/default/configmaps")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(PROPERTIES_CONFIGMAP_LIST))));
ConfigMapConfigProperties configMapConfigProperties = new ConfigMapConfigProperties();
configMapConfigProperties.setName("bootstrap-640");
@@ -107,7 +105,7 @@ class KubernetesClientConfigMapPropertySourceLocatorTests {
@Test
void locateWithSources() {
CoreV1Api api = new CoreV1Api();
stubFor(get(API)
stubFor(get("/api/v1/namespaces/default/configmaps")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(PROPERTIES_CONFIGMAP_LIST))));
ConfigMapConfigProperties configMapConfigProperties = new ConfigMapConfigProperties();
configMapConfigProperties.setName("fake-name");
@@ -135,7 +133,7 @@ class KubernetesClientConfigMapPropertySourceLocatorTests {
@Test
void testLocateWithoutNamespaceDeprecatedConstructor() {
CoreV1Api api = new CoreV1Api();
stubFor(get(API)
stubFor(get("/api/v1/namespaces/default/configmaps")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(PROPERTIES_CONFIGMAP_LIST))));
ConfigMapConfigProperties configMapConfigProperties = new ConfigMapConfigProperties();
configMapConfigProperties.setName("bootstrap-640");
@@ -157,7 +155,7 @@ class KubernetesClientConfigMapPropertySourceLocatorTests {
@Test
void testLocateWithoutNamespace() {
CoreV1Api api = new CoreV1Api();
stubFor(get(API)
stubFor(get("/api/v1/namespaces/default/configmaps")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(PROPERTIES_CONFIGMAP_LIST))));
ConfigMapConfigProperties configMapConfigProperties = new ConfigMapConfigProperties();
configMapConfigProperties.setName("bootstrap-640");

View File

@@ -65,8 +65,6 @@ class KubernetesClientConfigMapPropertySourceTests {
"dummy:\n property:\n string2: \"a\"\n int2: 1\n bool2: true\n")
.build());
private static final String API = "/api/v1/namespaces/default/configmaps";
private static WireMockServer wireMockServer;
@BeforeAll
@@ -94,11 +92,11 @@ class KubernetesClientConfigMapPropertySourceTests {
@Test
public void propertiesFile() {
CoreV1Api api = new CoreV1Api();
stubFor(get(API)
stubFor(get("/api/v1/namespaces/default/configmaps")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(PROPERTIES_CONFIGMAP_LIST))));
KubernetesClientConfigMapPropertySource propertySource = new KubernetesClientConfigMapPropertySource(api,
"bootstrap-640", "default", new MockEnvironment(), "");
verify(getRequestedFor(urlEqualTo(API)));
"bootstrap-640", "default", new MockEnvironment(), "", true);
verify(getRequestedFor(urlEqualTo("/api/v1/namespaces/default/configmaps")));
assertThat(propertySource.containsProperty("spring.cloud.kubernetes.configuration.watcher.refreshDelay"))
.isTrue();
assertThat(propertySource.getProperty("spring.cloud.kubernetes.configuration.watcher.refreshDelay"))
@@ -111,10 +109,11 @@ class KubernetesClientConfigMapPropertySourceTests {
@Test
public void yamlFile() {
CoreV1Api api = new CoreV1Api();
stubFor(get(API).willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(YAML_CONFIGMAP_LIST))));
stubFor(get("/api/v1/namespaces/default/configmaps")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(YAML_CONFIGMAP_LIST))));
KubernetesClientConfigMapPropertySource propertySource = new KubernetesClientConfigMapPropertySource(api,
"bootstrap-641", "default", new MockEnvironment(), "");
verify(getRequestedFor(urlEqualTo(API)));
"bootstrap-641", "default", new MockEnvironment(), "", true);
verify(getRequestedFor(urlEqualTo("/api/v1/namespaces/default/configmaps")));
assertThat(propertySource.containsProperty("dummy.property.string2")).isTrue();
assertThat(propertySource.getProperty("dummy.property.string2")).isEqualTo("a");
assertThat(propertySource.containsProperty("dummy.property.int2")).isTrue();
@@ -127,11 +126,11 @@ class KubernetesClientConfigMapPropertySourceTests {
@Test
public void propertiesFileWithPrefix() {
CoreV1Api api = new CoreV1Api();
stubFor(get(API)
stubFor(get("/api/v1/namespaces/default/configmaps")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(PROPERTIES_CONFIGMAP_LIST))));
KubernetesClientConfigMapPropertySource propertySource = new KubernetesClientConfigMapPropertySource(api,
"bootstrap-640", "default", new MockEnvironment(), "prefix");
verify(getRequestedFor(urlEqualTo(API)));
"bootstrap-640", "default", new MockEnvironment(), "prefix", true);
verify(getRequestedFor(urlEqualTo("/api/v1/namespaces/default/configmaps")));
assertThat(propertySource.containsProperty("prefix.spring.cloud.kubernetes.configuration.watcher.refreshDelay"))
.isTrue();
assertThat(propertySource.getProperty("prefix.spring.cloud.kubernetes.configuration.watcher.refreshDelay"))

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2013-2021 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
*
* https://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.client.config.applications.config_map_name_as_prefix;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.kubernetes.client.config.applications.config_map_name_as_prefix.properties.One;
import org.springframework.cloud.kubernetes.client.config.applications.config_map_name_as_prefix.properties.Three;
import org.springframework.cloud.kubernetes.client.config.applications.config_map_name_as_prefix.properties.Two;
@SpringBootApplication
@EnableConfigurationProperties({ One.class, Two.class, Three.class })
public class WithPrefixApp {
public static void main(String[] args) {
SpringApplication.run(WithPrefixApp.class, args);
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2013-2020 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
*
* https://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.client.config.applications.config_map_name_as_prefix.controller;
import org.springframework.cloud.kubernetes.client.config.applications.config_map_name_as_prefix.properties.One;
import org.springframework.cloud.kubernetes.client.config.applications.config_map_name_as_prefix.properties.Three;
import org.springframework.cloud.kubernetes.client.config.applications.config_map_name_as_prefix.properties.Two;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class Controller {
private final One one;
private final Two two;
private final Three three;
public Controller(One one, Two two, Three three) {
this.one = one;
this.two = two;
this.three = three;
}
@GetMapping("/prefix/one")
public String one() {
return one.getProperty();
}
@GetMapping("/prefix/two")
public String two() {
return two.getProperty();
}
@GetMapping("/prefix/three")
public String three() {
return three.getProperty();
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2013-2021 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
*
* https://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.client.config.applications.config_map_name_as_prefix.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("one")
public class One {
private String property;
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2013-2021 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
*
* https://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.client.config.applications.config_map_name_as_prefix.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "config-map-three")
public class Three {
private String property;
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2013-2021 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
*
* https://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.client.config.applications.config_map_name_as_prefix.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("two")
public class Two {
private String property;
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2013-2021 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
*
* https://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.client.config.applications.include_profile_specific_sources;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.kubernetes.client.config.applications.include_profile_specific_sources.properties.One;
import org.springframework.cloud.kubernetes.client.config.applications.include_profile_specific_sources.properties.Three;
import org.springframework.cloud.kubernetes.client.config.applications.include_profile_specific_sources.properties.Two;
@SpringBootApplication
@EnableConfigurationProperties({ One.class, Two.class, Three.class })
public class IncludeProfileSpecificSourcesApp {
public static void main(String[] args) {
SpringApplication.run(IncludeProfileSpecificSourcesApp.class, args);
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2013-2021 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
*
* https://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.client.config.applications.include_profile_specific_sources.controller;
import org.springframework.cloud.kubernetes.client.config.applications.include_profile_specific_sources.properties.One;
import org.springframework.cloud.kubernetes.client.config.applications.include_profile_specific_sources.properties.Three;
import org.springframework.cloud.kubernetes.client.config.applications.include_profile_specific_sources.properties.Two;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class IncludeProfileSpecificSourcesController {
private final One one;
private final Two two;
private final Three three;
public IncludeProfileSpecificSourcesController(One one, Two two, Three three) {
this.one = one;
this.two = two;
this.three = three;
}
@GetMapping("/profile-specific/one")
public String one() {
return one.getProperty();
}
@GetMapping("/profile-specific/two")
public String two() {
return two.getProperty();
}
@GetMapping("/profile-specific/three")
public String three() {
return three.getProperty();
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2013-2021 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
*
* https://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.client.config.applications.include_profile_specific_sources.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("one")
public class One {
private String property;
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2013-2021 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
*
* https://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.client.config.applications.include_profile_specific_sources.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("three")
public class Three {
private String property;
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2013-2021 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
*
* https://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.client.config.applications.include_profile_specific_sources.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("two")
public class Two {
private String property;
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright 2013-2020 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
*
* https://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.client.config.boostrap.stubs;
import java.util.Arrays;
import java.util.Collections;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.JSON;
import io.kubernetes.client.openapi.models.V1ConfigMap;
import io.kubernetes.client.openapi.models.V1ConfigMapBuilder;
import io.kubernetes.client.openapi.models.V1ConfigMapList;
import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder;
import io.kubernetes.client.util.ClientBuilder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
/**
* A test bootstrap that takes care to initialize ApiClient _before_ our main bootstrap
* context; with some stub data already present.
*
* @author wind57
*/
@Order(0)
@Configuration
@ConditionalOnProperty("config.map.name.as.prefix.stub")
public class ConfigMapNameAsPrefixConfigurationStub {
@Bean
public WireMockServer wireMock() {
WireMockServer server = new WireMockServer(options().dynamicPort());
server.start();
WireMock.configureFor("localhost", server.port());
return server;
}
@Bean
public ApiClient apiClient(WireMockServer wireMockServer) {
ApiClient apiClient = new ClientBuilder().setBasePath("http://localhost:" + wireMockServer.port()).build();
io.kubernetes.client.openapi.Configuration.setDefaultApiClient(apiClient);
apiClient.setDebugging(true);
stubData();
return apiClient;
}
private void stubData() {
V1ConfigMap one = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("config-map-one").withNamespace("spring-k8s")
.withResourceVersion("1").build())
.addToData(Collections.singletonMap("one.property", "one")).build();
V1ConfigMap two = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("config-map-two").withNamespace("spring-k8s")
.withResourceVersion("1").build())
.addToData(Collections.singletonMap("property", "two")).build();
V1ConfigMap three = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("config-map-three").withNamespace("spring-k8s")
.withResourceVersion("1").build())
.addToData(Collections.singletonMap("property", "three")).build();
V1ConfigMapList allConfigMaps = new V1ConfigMapList();
allConfigMaps.setItems(Arrays.asList(one, two, three));
// the actual stub for CoreV1Api calls
WireMock.stubFor(WireMock.get("/api/v1/namespaces/spring-k8s/configmaps")
.willReturn(WireMock.aResponse().withStatus(200).withBody(new JSON().serialize(allConfigMaps))));
}
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright 2013-2020 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
*
* https://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.client.config.boostrap.stubs;
import java.util.Arrays;
import java.util.Collections;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.JSON;
import io.kubernetes.client.openapi.models.V1ConfigMap;
import io.kubernetes.client.openapi.models.V1ConfigMapBuilder;
import io.kubernetes.client.openapi.models.V1ConfigMapList;
import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder;
import io.kubernetes.client.util.ClientBuilder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
/**
* A test bootstrap that takes care to initialize ApiClient _before_ our main bootstrap
* context; with some stub data already.
*
* @author wind57
*/
@Order(0)
@Configuration
@ConditionalOnProperty("include.profile.specific.sources")
class IncludeProfileSpecificSourcesConfigurationStub {
@Bean
public WireMockServer wireMock() {
WireMockServer server = new WireMockServer(options().dynamicPort());
server.start();
WireMock.configureFor("localhost", server.port());
return server;
}
@Bean
public ApiClient apiClient(WireMockServer wireMockServer) {
ApiClient apiClient = new ClientBuilder().setBasePath("http://localhost:" + wireMockServer.port()).build();
io.kubernetes.client.openapi.Configuration.setDefaultApiClient(apiClient);
apiClient.setDebugging(true);
stubData();
return apiClient;
}
private void stubData() {
V1ConfigMap one = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("config-map-one-dev").withNamespace("spring-k8s")
.withResourceVersion("1").build())
.addToData(Collections.singletonMap("one.property", "one")).build();
V1ConfigMap two = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("config-map-two").withNamespace("spring-k8s")
.withResourceVersion("1").build())
.addToData(Collections.singletonMap("two.property", "two")).build();
V1ConfigMap twoDev = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("config-map-two-dev").withNamespace("spring-k8s")
.withResourceVersion("1").build())
.addToData(Collections.singletonMap("two.property", "twoDev")).build();
V1ConfigMap three = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("config-map-three").withNamespace("spring-k8s")
.withResourceVersion("1").build())
.addToData(Collections.singletonMap("three.property", "three")).build();
V1ConfigMap threeDev = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("config-map-three-dev").withNamespace("spring-k8s")
.withResourceVersion("1").build())
.addToData(Collections.singletonMap("three.property", "threeDev")).build();
V1ConfigMapList allConfigMaps = new V1ConfigMapList();
allConfigMaps.setItems(Arrays.asList(one, two, twoDev, three, threeDev));
// the actual stub for CoreV1Api calls
WireMock.stubFor(WireMock.get("/api/v1/namespaces/spring-k8s/configmaps")
.willReturn(WireMock.aResponse().withStatus(200).withBody(new JSON().serialize(allConfigMaps))));
}
}

View File

@@ -0,0 +1,3 @@
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.kubernetes.client.config.boostrap.stubs.IncludeProfileSpecificSourcesConfigurationStub, \
org.springframework.cloud.kubernetes.client.config.boostrap.stubs.ConfigMapNameAsPrefixConfigurationStub

View File

@@ -0,0 +1,14 @@
spring:
application:
name: include-profile-specific-sources
cloud:
kubernetes:
config:
includeProfileSpecificSources: false
namespace: spring-k8s
sources:
- name: config-map-one
includeProfileSpecificSources: true
- name: config-map-two
includeProfileSpecificSources: false
- name: config-map-three

View File

@@ -29,8 +29,12 @@ public abstract class AbstractConfigProperties {
protected String namespace;
// use config map name to prefix properties
protected boolean useNameAsPrefix;
// use profile name to append config map name
protected boolean includeProfileSpecificSources = true;
public abstract String getConfigurationTarget();
public boolean isEnabled() {
@@ -65,4 +69,11 @@ public abstract class AbstractConfigProperties {
this.useNameAsPrefix = useNameAsPrefix;
}
public boolean isIncludeProfileSpecificSources() {
return includeProfileSpecificSources;
}
public void setIncludeProfileSpecificSources(boolean includeProfileSpecificSources) {
this.includeProfileSpecificSources = includeProfileSpecificSources;
}
}

View File

@@ -82,10 +82,11 @@ public class ConfigMapConfigProperties extends AbstractConfigProperties {
"'spring.cloud.kubernetes.config.useNameAsPrefix' is set to 'true', but 'spring.cloud.kubernetes.config.sources'"
+ " is empty; as such will default 'useNameAsPrefix' to 'false'");
}
return Collections.singletonList(new NormalizedSource(name, namespace, ""));
return Collections.singletonList(new NormalizedSource(name, namespace, "", includeProfileSpecificSources));
}
return sources.stream().map(s -> s.normalize(name, namespace, useNameAsPrefix)).collect(Collectors.toList());
return sources.stream().map(s -> s.normalize(name, namespace, useNameAsPrefix, includeProfileSpecificSources))
.collect(Collectors.toList());
}
@Override
@@ -114,6 +115,12 @@ public class ConfigMapConfigProperties extends AbstractConfigProperties {
*/
private Boolean useNameAsPrefix;
/**
* Use profile name to append to a config map name. Can't be a primitive, we need to
* know if it was explicitly set or not
*/
protected Boolean includeProfileSpecificSources;
/**
* An explicit prefix to be used for properties.
*/
@@ -160,6 +167,14 @@ public class ConfigMapConfigProperties extends AbstractConfigProperties {
this.explicitPrefix = explicitPrefix;
}
public Boolean getIncludeProfileSpecificSources() {
return includeProfileSpecificSources;
}
public void setIncludeProfileSpecificSources(Boolean includeProfileSpecificSources) {
this.includeProfileSpecificSources = includeProfileSpecificSources;
}
public boolean isEmpty() {
return !StringUtils.hasLength(this.name) && !StringUtils.hasLength(this.namespace);
}
@@ -169,15 +184,18 @@ public class ConfigMapConfigProperties extends AbstractConfigProperties {
public NormalizedSource normalize(String defaultName, String defaultNamespace) {
String normalizedName = StringUtils.hasLength(this.name) ? this.name : defaultName;
String normalizedNamespace = StringUtils.hasLength(this.namespace) ? this.namespace : defaultNamespace;
return new NormalizedSource(normalizedName, normalizedNamespace, "");
return new NormalizedSource(normalizedName, normalizedNamespace, "", true);
}
public NormalizedSource normalize(String defaultName, String defaultNamespace, boolean defaultUseNameAsPrefix) {
public NormalizedSource normalize(String defaultName, String defaultNamespace, boolean defaultUseNameAsPrefix,
boolean defaultIncludeProfileSpecificSources) {
String normalizedName = StringUtils.hasLength(this.name) ? this.name : defaultName;
String normalizedNamespace = StringUtils.hasLength(this.namespace) ? this.namespace : defaultNamespace;
String prefix = ConfigUtils.findPrefix(this.explicitPrefix, useNameAsPrefix, defaultUseNameAsPrefix,
normalizedName);
return new NormalizedSource(normalizedName, normalizedNamespace, prefix);
boolean includeProfileSpecificSources = ConfigUtils.includeProfileSpecificSources(defaultIncludeProfileSpecificSources,
this.includeProfileSpecificSources);
return new NormalizedSource(normalizedName, normalizedNamespace, prefix, includeProfileSpecificSources);
}
@Override
@@ -207,18 +225,22 @@ public class ConfigMapConfigProperties extends AbstractConfigProperties {
private final String prefix;
private final boolean includeProfileSpecificSources;
// not used, but not removed because of potential compatibility reasons
@Deprecated
NormalizedSource(String name, String namespace) {
this.name = name;
this.namespace = namespace;
this.prefix = "";
this.includeProfileSpecificSources = true;
}
NormalizedSource(String name, String namespace, String prefix) {
NormalizedSource(String name, String namespace, String prefix, boolean includeProfileSpecificSources) {
this.name = name;
this.namespace = namespace;
this.prefix = Objects.requireNonNull(prefix);
this.includeProfileSpecificSources = includeProfileSpecificSources;
}
public String getName() {
@@ -233,6 +255,10 @@ public class ConfigMapConfigProperties extends AbstractConfigProperties {
return prefix;
}
public boolean isIncludeProfileSpecificSources() {
return includeProfileSpecificSources;
}
@Override
public String toString() {
return "{ config-map name : '" + name + "', namespace : '" + namespace + "', prefix : '" + prefix + "' }";

View File

@@ -84,4 +84,19 @@ public final class ConfigUtils {
return "";
}
/**
* @param defaultIncludeProfileSpecificSources value of
* 'spring.cloud.kubernetes.config.includeProfileSpecificSources'
* @param includeProfileSpecificSources value of
* 'spring.cloud.kubernetes.config.sources.includeProfileSpecificSources'
* @return useProfileNameAsPrefix to be used in normalized sources
*/
public static boolean includeProfileSpecificSources(boolean defaultIncludeProfileSpecificSources,
Boolean includeProfileSpecificSources) {
if (includeProfileSpecificSources != null) {
return includeProfileSpecificSources;
}
return defaultIncludeProfileSpecificSources;
}
}

View File

@@ -218,4 +218,114 @@ public class ConfigMapConfigPropertiesTests {
Assertions.assertEquals(sources.get(3).getPrefix(), "");
}
/**
* <pre>
* spring:
* cloud:
* kubernetes:
* config:
* name: config-map-a
* namespace: spring-k8s
* </pre>
*
* a config as above will result in a NormalizedSource where includeProfileSpecificSources
* will be true (this test proves that the change we added is not a breaking change
* for the already existing functionality)
*/
@Test
public void testUseIncludeProfileSpecificSourcesNoChanges() {
ConfigMapConfigProperties properties = new ConfigMapConfigProperties();
properties.setSources(Collections.emptyList());
properties.setName("config-map-a");
properties.setNamespace("spring-k8s");
List<ConfigMapConfigProperties.NormalizedSource> sources = properties.determineSources();
Assertions.assertEquals(sources.size(), 1, "empty sources must generate a List with a single NormalizedSource");
Assertions.assertTrue(sources.get(0).isIncludeProfileSpecificSources());
}
/**
* <pre>
* spring:
* cloud:
* kubernetes:
* config:
* includeProfileSpecificSources: false
* name: config-map-a
* namespace: spring-k8s
* </pre>
*
* a config as above will result in a NormalizedSource where includeProfileSpecificSources
* will be false. Even if we did not define any sources explicitly, one will still be
* created, by default. That one might "flatMap" into multiple other, because of
* multiple profiles. As such this setting still matters and must be propagated to the
* normalized source.
*/
@Test
public void testUseIncludeProfileSpecificSourcesDefaultChanged() {
ConfigMapConfigProperties properties = new ConfigMapConfigProperties();
properties.setSources(Collections.emptyList());
properties.setName("config-map-a");
properties.setNamespace("spring-k8s");
properties.setIncludeProfileSpecificSources(false);
List<ConfigMapConfigProperties.NormalizedSource> sources = properties.determineSources();
Assertions.assertEquals(sources.size(), 1, "empty sources must generate a List with a single NormalizedSource");
Assertions.assertFalse(sources.get(0).isIncludeProfileSpecificSources());
}
/**
* <pre>
* spring:
* cloud:
* kubernetes:
* config:
* includeProfileSpecificSources: false
* name: config-map-a
* namespace: spring-k8s
* sources:
* - name: one
* includeProfileSpecificSources: true
* - name: two
* - name: three
* includeProfileSpecificSources: false
* </pre>
*
* <pre>
* source "one" will have "includeProfileSpecificSources = true".
* source "two" will have "includeProfileSpecificSources = false".
* source "three" will have "includeProfileSpecificSources = false".
* </pre>
*/
@Test
public void testUseIncludeProfileSpecificSourcesDefaultChangedSourceOverride() {
ConfigMapConfigProperties properties = new ConfigMapConfigProperties();
properties.setSources(Collections.emptyList());
properties.setName("config-map-a");
properties.setNamespace("spring-k8s");
properties.setIncludeProfileSpecificSources(false);
ConfigMapConfigProperties.Source one = new ConfigMapConfigProperties.Source();
one.setName("config-map-one");
one.setIncludeProfileSpecificSources(true);
ConfigMapConfigProperties.Source two = new ConfigMapConfigProperties.Source();
two.setName("config-map-two");
ConfigMapConfigProperties.Source three = new ConfigMapConfigProperties.Source();
three.setName("config-map-three");
three.setIncludeProfileSpecificSources(false);
properties.setSources(Arrays.asList(one, two, three));
List<ConfigMapConfigProperties.NormalizedSource> sources = properties.determineSources();
Assertions.assertEquals(sources.size(), 3);
Assertions.assertTrue(sources.get(0).isIncludeProfileSpecificSources());
Assertions.assertFalse(sources.get(1).isIncludeProfileSpecificSources());
Assertions.assertFalse(sources.get(2).isIncludeProfileSpecificSources());
}
}

View File

@@ -54,4 +54,55 @@ public class ConfigUtilsTests {
Assertions.assertEquals(result, "");
}
/**
* <pre>
* spring:
* cloud:
* kubernetes:
* config:
* includeProfileSpecificSources: true
* </pre>
*
* above will generate "true" for a normalized source
*/
@Test
public void testUseIncludeProfileSpecificSourcesOnlyDefaultSet() {
Assertions.assertTrue(ConfigUtils.includeProfileSpecificSources(true, null));
}
/**
* <pre>
* spring:
* cloud:
* kubernetes:
* config:
* includeProfileSpecificSources: true
* </pre>
*
* above will generate "false" for a normalized source
*/
@Test
public void testUseIncludeProfileSpecificSourcesOnlyDefaultNotSet() {
Assertions.assertFalse(ConfigUtils.includeProfileSpecificSources(false, null));
}
/**
* <pre>
* spring:
* cloud:
* kubernetes:
* config:
* includeProfileSpecificSources: true
* sources:
* - name: one
* includeProfileSpecificSources: false
* </pre>
*
* above will generate "false" for a normalized source
*/
@Test
public void testUseIncludeProfileSpecificSourcesSourcesOverridesDefault() {
Assertions.assertFalse(ConfigUtils.includeProfileSpecificSources(true, false));
}
}

View File

@@ -44,7 +44,7 @@ public class Fabric8ConfigMapPropertySource extends ConfigMapPropertySource {
private static final Log LOG = LogFactory.getLog(Fabric8ConfigMapPropertySource.class);
public Fabric8ConfigMapPropertySource(KubernetesClient client, String name) {
this(client, name, null, null, "");
this(client, name, null, null, "", true);
}
/**
@@ -52,27 +52,27 @@ public class Fabric8ConfigMapPropertySource extends ConfigMapPropertySource {
* discouraged.
*/
@Deprecated
public Fabric8ConfigMapPropertySource(KubernetesClient client, String applicationName, String namespace,
public Fabric8ConfigMapPropertySource(KubernetesClient client, String name, String namespace,
Environment environment) {
super(getName(applicationName, getApplicationNamespace(client, namespace)),
getData(client, applicationName, getApplicationNamespace(client, namespace), environment, ""));
super(getName(name, getApplicationNamespace(client, namespace)),
getData(client, name, getApplicationNamespace(client, namespace), environment, "", true));
}
public Fabric8ConfigMapPropertySource(KubernetesClient client, String applicationName, String namespace,
Environment environment, String prefix) {
super(getName(applicationName, getApplicationNamespace(client, namespace)),
getData(client, applicationName, getApplicationNamespace(client, namespace), environment, prefix));
public Fabric8ConfigMapPropertySource(KubernetesClient client, String name, String namespace,
Environment environment, String prefix, boolean includeProfileSpecificSources) {
super(getName(name, getApplicationNamespace(client, namespace)), getData(client, name,
getApplicationNamespace(client, namespace), environment, prefix, includeProfileSpecificSources));
}
private static Map<String, Object> getData(KubernetesClient client, String applicationName, String namespace,
Environment environment, String prefix) {
private static Map<String, Object> getData(KubernetesClient client, String name, String namespace,
Environment environment, String prefix, boolean includeProfileSpecificSources) {
try {
Map<String, String> data = getConfigMapData(client, namespace, applicationName);
Map<String, String> data = getConfigMapData(client, namespace, name);
Map<String, Object> result = new HashMap<>(processAllEntries(data, environment));
if (environment != null) {
if (environment != null && includeProfileSpecificSources) {
for (String activeProfile : environment.getActiveProfiles()) {
String mapNameWithProfile = applicationName + "-" + activeProfile;
String mapNameWithProfile = name + "-" + activeProfile;
Map<String, String> dataWithProfile = getConfigMapData(client, namespace, mapNameWithProfile);
result.putAll(processAllEntries(dataWithProfile, environment));
}
@@ -88,8 +88,7 @@ public class Fabric8ConfigMapPropertySource extends ConfigMapPropertySource {
}
catch (Exception e) {
LOG.warn("Can't read configMap with name: [" + applicationName + "] in namespace: [" + namespace
+ "]. Ignoring.", e);
LOG.warn("Can't read configMap with name: [" + name + "] in namespace: [" + namespace + "]. Ignoring.", e);
}
return Collections.emptyMap();

View File

@@ -69,7 +69,7 @@ public class Fabric8ConfigMapPropertySourceLocator extends ConfigMapPropertySour
String namespace = getApplicationNamespace(this.client, normalizedSource.getNamespace(), configurationTarget,
provider);
return new Fabric8ConfigMapPropertySource(this.client, applicationName, namespace, environment,
normalizedSource.getPrefix());
normalizedSource.getPrefix(), normalizedSource.isIncludeProfileSpecificSources());
}
}

View File

@@ -0,0 +1,151 @@
/*
* Copyright 2013-2021 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
*
* https://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.fabric8.config;
import java.util.HashMap;
import java.util.Map;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
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.fabric8.config.include_profile_specific_sources.IncludeProfileSpecificSourcesApp;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.reactive.server.WebTestClient;
/**
* @author wind57
*/
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = IncludeProfileSpecificSourcesApp.class,
properties = { "spring.cloud.bootstrap.name=include-profile-specific-sources" })
@AutoConfigureWebTestClient
@EnableKubernetesMockClient(crud = true, https = false)
@ActiveProfiles("dev")
class ConfigMapWithIncludeProfileSpecificSourcesTests {
private static KubernetesClient mockClient;
@Autowired
private WebTestClient webClient;
@BeforeAll
public static void setUpBeforeClass() {
// 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");
System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true");
Map<String, String> one = new HashMap<>();
one.put("one.property", "one");
createConfigmap("config-map-one-dev", one);
Map<String, String> two = new HashMap<>();
two.put("two.property", "two");
createConfigmap("config-map-two", two);
Map<String, String> twoDev = new HashMap<>();
twoDev.put("two.property", "twoDev");
createConfigmap("config-map-two-dev", twoDev);
Map<String, String> three = new HashMap<>();
three.put("three.property", "three");
createConfigmap("config-map-three", three);
Map<String, String> threeDev = new HashMap<>();
threeDev.put("three.property", "threeDev");
createConfigmap("config-map-three-dev", threeDev);
}
private static void createConfigmap(String name, Map<String, String> data) {
mockClient.configMaps().inNamespace("spring-k8s").createNew().withNewMetadata().withName(name).endMetadata()
.addToData(data).done();
}
/**
* <pre>
* 'spring.cloud.kubernetes.config.includeProfileSpecificSources=false'
* 'spring.cloud.kubernetes.config.sources[0].includeProfileSpecificSources=true'
* 'spring.cloud.kubernetes.config.sources[0].name=config-map-one'
*
* We do not define config-map 'config-map-one', but we do define 'config-map-one-dev'.
*
* As such: @ConfigurationProperties("one") must be resolved from 'config-map-one-dev'
* </pre>
*/
@Test
public void testOne() {
this.webClient.get().uri("/profile-specific/one").exchange().expectStatus().isOk().expectBody(String.class)
.value(Matchers.equalTo("one"));
}
/**
* <pre>
* 'spring.cloud.kubernetes.config.includeProfileSpecificSources=false'
* 'spring.cloud.kubernetes.config.sources[1].includeProfileSpecificSources=false'
* 'spring.cloud.kubernetes.config.sources[1].name=config-map-two'
*
* We define config-map 'config-map-two', but we also define 'config-map-two-dev'.
* This tests proves that data will be read from 'config-map-two' _only_, even if 'config-map-two-dev'
* also exists. This happens because of the 'includeProfileSpecificSources=false' property defined at the source level.
* If this would be incorrect, the value we read from '/profile-specific/two' would have been 'twoDev' and _not_ 'two',
* simply because 'config-map-two-dev' would override the property value.
*
* As such: @ConfigurationProperties("two") must be resolved from 'config-map-two'
* </pre>
*/
@Test
public void testTwo() {
this.webClient.get().uri("/profile-specific/two").exchange().expectStatus().isOk().expectBody(String.class)
.value(Matchers.equalTo("two"));
}
/**
* <pre>
* 'spring.cloud.kubernetes.config.includeProfileSpecificSources=false'
* 'spring.cloud.kubernetes.config.sources[2].name=config-map-three'
*
* We define config-map 'config-map-three', but we also define 'config-map-three-dev'.
* This tests proves that data will be read from 'config-map-three' _only_, even if 'config-map-three-dev'
* also exists. This happens because the 'includeProfileSpecificSources' property is not defined at the source level,
* but it is defaulted from the root level, where we set it to false.
* If this would be incorrect, the value we read from '/profile-specific/three' would have been 'threeDev' and _not_ 'three',
* simply because 'config-map-three-dev' would override the property value.
*
* As such: @ConfigurationProperties("three") must be resolved from 'config-map-three'
* </pre>
*/
@Test
public void testThree() {
this.webClient.get().uri("/profile-specific/three").exchange().expectStatus().isOk().expectBody(String.class)
.value(Matchers.equalTo("three"));
}
}

View File

@@ -39,10 +39,10 @@ import org.springframework.test.web.reactive.server.WebTestClient;
*/
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = WithPrefixApp.class,
properties = { "spring.cloud.bootstrap.name=same-key-with-prefix" })
properties = { "spring.cloud.bootstrap.name=config-map-name-as-prefix" })
@AutoConfigureWebTestClient
@EnableKubernetesMockClient(crud = true, https = false)
public class ConfigMapWithPrefixTests {
class ConfigMapWithPrefixTests {
private static KubernetesClient mockClient;
@@ -62,21 +62,20 @@ public class ConfigMapWithPrefixTests {
Map<String, String> one = new HashMap<>();
one.put("one.property", "one");
createConfigmap(mockClient, "config-map-one", one);
createConfigmap("config-map-one", one);
Map<String, String> two = new HashMap<>();
two.put("property", "two");
createConfigmap(mockClient, "config-map-two", two);
createConfigmap("config-map-two", two);
Map<String, String> three = new HashMap<>();
three.put("property", "three");
createConfigmap(mockClient, "config-map-three", three);
createConfigmap("config-map-three", three);
}
private static void createConfigmap(KubernetesClient client, String name, Map<String, String> data) {
client.configMaps().inNamespace("spring-k8s").createNew().withNewMetadata().withName(name).endMetadata()
private static void createConfigmap(String name, Map<String, String> data) {
mockClient.configMaps().inNamespace("spring-k8s").createNew().withNewMetadata().withName(name).endMetadata()
.addToData(data).done();
}
@@ -91,7 +90,7 @@ public class ConfigMapWithPrefixTests {
*/
@Test
public void testOne() {
this.webClient.get().uri("/one").exchange().expectStatus().isOk().expectBody(String.class)
this.webClient.get().uri("/prefix/one").exchange().expectStatus().isOk().expectBody(String.class)
.value(Matchers.equalTo("one"));
}
@@ -106,7 +105,7 @@ public class ConfigMapWithPrefixTests {
*/
@Test
public void testTwo() {
this.webClient.get().uri("/two").exchange().expectStatus().isOk().expectBody(String.class)
this.webClient.get().uri("/prefix/two").exchange().expectStatus().isOk().expectBody(String.class)
.value(Matchers.equalTo("two"));
}
@@ -121,7 +120,7 @@ public class ConfigMapWithPrefixTests {
*/
@Test
public void testThree() {
this.webClient.get().uri("/three").exchange().expectStatus().isOk().expectBody(String.class)
this.webClient.get().uri("/prefix/three").exchange().expectStatus().isOk().expectBody(String.class)
.value(Matchers.equalTo("three"));
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2013-2021 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
*
* https://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.fabric8.config.include_profile_specific_sources;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.kubernetes.fabric8.config.include_profile_specific_sources.properties.One;
import org.springframework.cloud.kubernetes.fabric8.config.include_profile_specific_sources.properties.Three;
import org.springframework.cloud.kubernetes.fabric8.config.include_profile_specific_sources.properties.Two;
@SpringBootApplication
@EnableConfigurationProperties({ One.class, Two.class, Three.class })
public class IncludeProfileSpecificSourcesApp {
public static void main(String[] args) {
SpringApplication.run(IncludeProfileSpecificSourcesApp.class, args);
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2013-2021 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
*
* https://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.fabric8.config.include_profile_specific_sources.controller;
import org.springframework.cloud.kubernetes.fabric8.config.include_profile_specific_sources.properties.One;
import org.springframework.cloud.kubernetes.fabric8.config.include_profile_specific_sources.properties.Three;
import org.springframework.cloud.kubernetes.fabric8.config.include_profile_specific_sources.properties.Two;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class IncludeProfileSpecificSourcesController {
private final One one;
private final Two two;
private final Three three;
public IncludeProfileSpecificSourcesController(One one, Two two, Three three) {
this.one = one;
this.two = two;
this.three = three;
}
@GetMapping("/profile-specific/one")
public String one() {
return one.getProperty();
}
@GetMapping("/profile-specific/two")
public String two() {
return two.getProperty();
}
@GetMapping("/profile-specific/three")
public String three() {
return three.getProperty();
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2013-2021 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
*
* https://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.fabric8.config.include_profile_specific_sources.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("one")
public class One {
private String property;
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2013-2021 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
*
* https://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.fabric8.config.include_profile_specific_sources.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("three")
public class Three {
private String property;
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2013-2021 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
*
* https://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.fabric8.config.include_profile_specific_sources.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("two")
public class Two {
private String property;
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
}

View File

@@ -37,17 +37,17 @@ public class Controller {
this.three = three;
}
@GetMapping("/one")
@GetMapping("/prefix/one")
public String one() {
return one.getProperty();
}
@GetMapping("/two")
@GetMapping("/prefix/two")
public String two() {
return two.getProperty();
}
@GetMapping("/three")
@GetMapping("/prefix/three")
public String three() {
return three.getProperty();
}

View File

@@ -0,0 +1,14 @@
spring:
application:
name: with-prefix
cloud:
kubernetes:
config:
useNameAsPrefix: true
namespace: spring-k8s
sources:
- name: config-map-one
useNameAsPrefix: false
- name: config-map-two
explicitPrefix: two
- name: config-map-three

View File

@@ -0,0 +1,14 @@
spring:
application:
name: include-profile-specific-sources
cloud:
kubernetes:
config:
includeProfileSpecificSources: false
namespace: spring-k8s
sources:
- name: config-map-one
includeProfileSpecificSources: true
- name: config-map-two
includeProfileSpecificSources: false
- name: config-map-three