diff --git a/docs/src/main/asciidoc/property-source-config.adoc b/docs/src/main/asciidoc/property-source-config.adoc index 41ba331f..59a70264 100644 --- a/docs/src/main/asciidoc/property-source-config.adoc +++ b/docs/src/main/asciidoc/property-source-config.adoc @@ -867,6 +867,11 @@ such a method. This, in turn, could be configured via environment properties. Fo Failure to find a namespace from the above steps will result in an Exception being raised. +[[order_of_configMaps_and_secrets]] +=== Order of ConfigMaps and Secrets + +If, for whatever reason, you enabled both configmaps and secrets, and there is a common property between them, the value from the ConfigMap will have a higher precedence. That is: it will override whatever values are found in secrets. + === `PropertySource` Reload WARNING: This functionality has been deprecated in the 2020.0 release. Please see diff --git a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapPropertySourceLocator.java b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapPropertySourceLocator.java index d039f3ef..56a17e6a 100644 --- a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapPropertySourceLocator.java +++ b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapPropertySourceLocator.java @@ -22,6 +22,7 @@ import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider; import org.springframework.cloud.kubernetes.commons.config.ConfigMapConfigProperties; import org.springframework.cloud.kubernetes.commons.config.ConfigMapPropertySourceLocator; import org.springframework.cloud.kubernetes.commons.config.NormalizedSource; +import org.springframework.core.annotation.Order; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; @@ -31,6 +32,7 @@ import static org.springframework.cloud.kubernetes.client.KubernetesClientUtils. * @author Ryan Baxter * @author Isik Erhan */ +@Order(0) public class KubernetesClientConfigMapPropertySourceLocator extends ConfigMapPropertySourceLocator { private final CoreV1Api coreV1Api; diff --git a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientSecretsPropertySourceLocator.java b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientSecretsPropertySourceLocator.java index 575d1640..057a43d9 100644 --- a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientSecretsPropertySourceLocator.java +++ b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientSecretsPropertySourceLocator.java @@ -23,6 +23,7 @@ import org.springframework.cloud.kubernetes.commons.config.NormalizedSource; import org.springframework.cloud.kubernetes.commons.config.SecretsConfigProperties; import org.springframework.cloud.kubernetes.commons.config.SecretsPropertySource; import org.springframework.cloud.kubernetes.commons.config.SecretsPropertySourceLocator; +import org.springframework.core.annotation.Order; import org.springframework.core.env.ConfigurableEnvironment; import static org.springframework.cloud.kubernetes.client.KubernetesClientUtils.getApplicationNamespace; @@ -31,6 +32,7 @@ import static org.springframework.cloud.kubernetes.client.KubernetesClientUtils. * @author Ryan Baxter * @author Isik Erhan */ +@Order(1) public class KubernetesClientSecretsPropertySourceLocator extends SecretsPropertySourceLocator { private final CoreV1Api coreV1Api; diff --git a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/RetryableKubernetesClientConfigMapPropertySourceLocator.java b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/RetryableKubernetesClientConfigMapPropertySourceLocator.java index cb4d4021..7e8bfbc1 100644 --- a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/RetryableKubernetesClientConfigMapPropertySourceLocator.java +++ b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/RetryableKubernetesClientConfigMapPropertySourceLocator.java @@ -22,6 +22,7 @@ import io.kubernetes.client.openapi.apis.CoreV1Api; import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider; import org.springframework.cloud.kubernetes.commons.config.ConfigMapConfigProperties; +import org.springframework.core.annotation.Order; import org.springframework.core.env.Environment; import org.springframework.core.env.PropertySource; import org.springframework.retry.annotation.Retryable; @@ -31,8 +32,8 @@ import org.springframework.retry.annotation.Retryable; * * @author Ryan Baxter */ -public class RetryableKubernetesClientConfigMapPropertySourceLocator - extends KubernetesClientConfigMapPropertySourceLocator { +@Order(0) +class RetryableKubernetesClientConfigMapPropertySourceLocator extends KubernetesClientConfigMapPropertySourceLocator { RetryableKubernetesClientConfigMapPropertySourceLocator(CoreV1Api coreV1Api, ConfigMapConfigProperties properties, KubernetesNamespaceProvider kubernetesNamespaceProvider) { diff --git a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/RetryableKubernetesClientSecretsPropertySourceLocator.java b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/RetryableKubernetesClientSecretsPropertySourceLocator.java index a711b590..3f01b8cc 100644 --- a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/RetryableKubernetesClientSecretsPropertySourceLocator.java +++ b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/RetryableKubernetesClientSecretsPropertySourceLocator.java @@ -22,6 +22,7 @@ import io.kubernetes.client.openapi.apis.CoreV1Api; import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider; import org.springframework.cloud.kubernetes.commons.config.SecretsConfigProperties; +import org.springframework.core.annotation.Order; import org.springframework.core.env.Environment; import org.springframework.core.env.PropertySource; import org.springframework.retry.annotation.Retryable; @@ -31,8 +32,8 @@ import org.springframework.retry.annotation.Retryable; * * @author Ryan Baxter */ -public class RetryableKubernetesClientSecretsPropertySourceLocator - extends KubernetesClientSecretsPropertySourceLocator { +@Order(1) +class RetryableKubernetesClientSecretsPropertySourceLocator extends KubernetesClientSecretsPropertySourceLocator { RetryableKubernetesClientSecretsPropertySourceLocator(CoreV1Api coreV1Api, KubernetesNamespaceProvider kubernetesNamespaceProvider, SecretsConfigProperties secretsConfigProperties) { diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientSourcesOrderTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientSourcesOrderTests.java new file mode 100644 index 00000000..e141a251 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientSourcesOrderTests.java @@ -0,0 +1,79 @@ +/* + * Copyright 2013-2023 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.sources_order.SourcesOrderApp; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * The stub data for this test is in : + * {@link org.springframework.cloud.kubernetes.client.config.boostrap.stubs.SourcesOrderConfigurationStub} + * + * @author wind57 + */ +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SourcesOrderApp.class, + properties = { "spring.cloud.bootstrap.name=sources-order", "sources.order.stub=true" }) +@AutoConfigureWebTestClient +class KubernetesClientSourcesOrderTests { + + @Autowired + private WebTestClient webClient; + + @AfterEach + void afterEach() { + WireMock.reset(); + } + + @AfterAll + static void afterAll() { + WireMock.shutdownServer(); + } + + /** + *
+ * 1. There is one secret deployed: my-secret. It has two properties: {my.one=one, my.key=from-secret}
+ * 2. There is one configmap deployed: my-configmap. It has two properties: {my.two=two, my.key=from-configmap}
+ *
+ * We invoke three endpoints: /one, /two, /key.
+ * The first two prove that both the secret and configmap have been read, the last one proves that
+ * config maps have a higher precedence.
+ *
+ */
+ @Test
+ void test() {
+ this.webClient.get().uri("/one").exchange().expectStatus().isOk().expectBody(String.class)
+ .value(Matchers.equalTo("one"));
+ this.webClient.get().uri("/two").exchange().expectStatus().isOk().expectBody(String.class)
+ .value(Matchers.equalTo("two"));
+
+ this.webClient.get().uri("/key").exchange().expectStatus().isOk().expectBody(String.class)
+ .value(Matchers.equalTo("from-configmap"));
+ }
+
+}
diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/RetryableKubernetesClientSourcesOrderTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/RetryableKubernetesClientSourcesOrderTests.java
new file mode 100644
index 00000000..21826b07
--- /dev/null
+++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/RetryableKubernetesClientSourcesOrderTests.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2013-2023 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.sources_order.SourcesOrderApp;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.web.reactive.server.WebTestClient;
+
+/**
+ * The stub data for this test is in :
+ * {@link org.springframework.cloud.kubernetes.client.config.boostrap.stubs.SourcesOrderConfigurationStub}
+ *
+ * @author wind57
+ */
+@ExtendWith(SpringExtension.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SourcesOrderApp.class,
+ properties = { "spring.cloud.bootstrap.name=retryable-sources-order", "sources.order.stub=true" })
+@AutoConfigureWebTestClient
+class RetryableKubernetesClientSourcesOrderTests {
+
+ @Autowired
+ private WebTestClient webClient;
+
+ @AfterEach
+ void afterEach() {
+ WireMock.reset();
+ }
+
+ @AfterAll
+ static void afterAll() {
+ WireMock.shutdownServer();
+ }
+
+ /**
+ *
+ * 1. There is one secret deployed: my-secret. It has two properties: {my.one=one, my.key=from-secret}
+ * 2. There is one configmap deployed: my-configmap. It has two properties: {my.two=two, my.key=from-configmap}
+ *
+ * We invoke three endpoints: /one, /two, /key.
+ * The first two prove that both the secret and configmap have been read, the last one proves that
+ * config maps have a higher precedence.
+ *
+ */
+ @Test
+ void test() {
+ this.webClient.get().uri("/one").exchange().expectStatus().isOk().expectBody(String.class)
+ .value(Matchers.equalTo("one"));
+ this.webClient.get().uri("/two").exchange().expectStatus().isOk().expectBody(String.class)
+ .value(Matchers.equalTo("two"));
+
+ this.webClient.get().uri("/key").exchange().expectStatus().isOk().expectBody(String.class)
+ .value(Matchers.equalTo("from-configmap"));
+ }
+
+}
diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/sources_order/SourcesOrderApp.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/sources_order/SourcesOrderApp.java
new file mode 100644
index 00000000..757ddb63
--- /dev/null
+++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/sources_order/SourcesOrderApp.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2013-2023 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.sources_order;
+
+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.sources_order.properties.Properties;
+
+/**
+ * @author wind57
+ */
+@SpringBootApplication
+@EnableConfigurationProperties(Properties.class)
+public class SourcesOrderApp {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SourcesOrderApp.class, args);
+ }
+
+}
diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/sources_order/controller/SourcesOrderController.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/sources_order/controller/SourcesOrderController.java
new file mode 100644
index 00000000..e5288511
--- /dev/null
+++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/sources_order/controller/SourcesOrderController.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2013-2023 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.sources_order.controller;
+
+import org.springframework.cloud.kubernetes.client.config.applications.sources_order.properties.Properties;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author wind57
+ */
+@RestController
+public class SourcesOrderController {
+
+ private final Properties properties;
+
+ SourcesOrderController(Properties properties) {
+ this.properties = properties;
+ }
+
+ @GetMapping("/key")
+ String key() {
+ return properties.getKey();
+ }
+
+ @GetMapping("/one")
+ String one() {
+ return properties.getOne();
+ }
+
+ @GetMapping("/two")
+ String two() {
+ return properties.getTwo();
+ }
+
+}
diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/sources_order/properties/Properties.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/sources_order/properties/Properties.java
new file mode 100644
index 00000000..14b55ddb
--- /dev/null
+++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/sources_order/properties/Properties.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2013-2023 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.sources_order.properties;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * @author wind57
+ */
+@ConfigurationProperties("my")
+public class Properties {
+
+ private String key;
+
+ private String one;
+
+ private String two;
+
+ public String getKey() {
+ return key;
+ }
+
+ public void setKey(String key) {
+ this.key = key;
+ }
+
+ public String getOne() {
+ return one;
+ }
+
+ public void setOne(String one) {
+ this.one = one;
+ }
+
+ public String getTwo() {
+ return two;
+ }
+
+ public void setTwo(String two) {
+ this.two = two;
+ }
+
+}
diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/SourcesOrderConfigurationStub.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/SourcesOrderConfigurationStub.java
new file mode 100644
index 00000000..5c6e6b72
--- /dev/null
+++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/SourcesOrderConfigurationStub.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2013-2023 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.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+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.openapi.models.V1Secret;
+import io.kubernetes.client.openapi.models.V1SecretBuilder;
+import io.kubernetes.client.openapi.models.V1SecretList;
+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;
+
+@Order(0)
+@Configuration
+@ConditionalOnProperty("sources.order.stub")
+public class SourcesOrderConfigurationStub {
+
+ @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);
+ stubConfigMapData();
+ stubSecretsData();
+ return apiClient;
+ }
+
+ private void stubConfigMapData() {
+
+ Map
+ * 1. There is one secret deployed: my-secret. It has two properties: {my.one=one, my.key=from-secret}
+ * 2. There is one configmap deployed: my-configmap. It has two properties: {my.two=two, my.key=from-configmap}
+ *
+ * We invoke three endpoints: /one, /two, /key.
+ * The first two prove that both the secret and configmap have been read, the last one proves that
+ * config maps have a higher precedence.
+ *
+ */
+ @Test
+ void test() {
+ this.webClient.get().uri("/retryable-one").exchange().expectStatus().isOk().expectBody(String.class)
+ .value(Matchers.equalTo("one"));
+ this.webClient.get().uri("/retryable-two").exchange().expectStatus().isOk().expectBody(String.class)
+ .value(Matchers.equalTo("two"));
+
+ this.webClient.get().uri("/retryable-key").exchange().expectStatus().isOk().expectBody(String.class)
+ .value(Matchers.equalTo("from-configmap"));
+ }
+
+}
diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sources_order/Properties.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sources_order/Properties.java
new file mode 100644
index 00000000..eb55b259
--- /dev/null
+++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sources_order/Properties.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2013-2023 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.sources_order;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * @author wind57
+ */
+@ConfigurationProperties("my")
+class Properties {
+
+ private String key;
+
+ private String one;
+
+ private String two;
+
+ public String getKey() {
+ return key;
+ }
+
+ public void setKey(String key) {
+ this.key = key;
+ }
+
+ public String getOne() {
+ return one;
+ }
+
+ public void setOne(String one) {
+ this.one = one;
+ }
+
+ public String getTwo() {
+ return two;
+ }
+
+ public void setTwo(String two) {
+ this.two = two;
+ }
+
+}
diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sources_order/SourcesOrderApp.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sources_order/SourcesOrderApp.java
new file mode 100644
index 00000000..676acaab
--- /dev/null
+++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sources_order/SourcesOrderApp.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013-2023 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.sources_order;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+
+/**
+ * @author wind57
+ */
+@SpringBootApplication
+@EnableConfigurationProperties(Properties.class)
+class SourcesOrderApp {
+
+ static void main(String[] args) {
+ SpringApplication.run(SourcesOrderApp.class, args);
+ }
+
+}
diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sources_order/SourcesOrderController.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sources_order/SourcesOrderController.java
new file mode 100644
index 00000000..f9f277da
--- /dev/null
+++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sources_order/SourcesOrderController.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2013-2023 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.sources_order;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author wind57
+ */
+@RestController
+class SourcesOrderController {
+
+ private final Properties properties;
+
+ SourcesOrderController(Properties properties) {
+ this.properties = properties;
+ }
+
+ @GetMapping("/key")
+ String key() {
+ return properties.getKey();
+ }
+
+ @GetMapping("/one")
+ String one() {
+ return properties.getOne();
+ }
+
+ @GetMapping("/two")
+ String two() {
+ return properties.getTwo();
+ }
+
+}
diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sources_order/SourcesOrderTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sources_order/SourcesOrderTests.java
new file mode 100644
index 00000000..bd94b49b
--- /dev/null
+++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sources_order/SourcesOrderTests.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2013-2023 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.sources_order;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+
+import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
+import io.fabric8.kubernetes.api.model.SecretBuilder;
+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.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.web.reactive.server.WebTestClient;
+
+/**
+ * @author wind57
+ */
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SourcesOrderApp.class,
+ properties = { "spring.cloud.bootstrap.name=sources-order" })
+@EnableKubernetesMockClient(crud = true, https = false)
+class SourcesOrderTests {
+
+ private static KubernetesClient mockClient;
+
+ @Autowired
+ private WebTestClient webClient;
+
+ @BeforeAll
+ 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
+ * 1. There is one secret deployed: my-secret. It has two properties: {my.one=one, my.key=from-secret}
+ * 2. There is one configmap deployed: my-configmap. It has two properties: {my.two=two, my.key=from-configmap}
+ *
+ * We invoke three endpoints: /one, /two, /key.
+ * The first two prove that both the secret and configmap have been read, the last one proves that
+ * config maps have a higher precedence.
+ *
+ */
+ @Test
+ void test() {
+ this.webClient.get().uri("/one").exchange().expectStatus().isOk().expectBody(String.class)
+ .value(Matchers.equalTo("one"));
+ this.webClient.get().uri("/two").exchange().expectStatus().isOk().expectBody(String.class)
+ .value(Matchers.equalTo("two"));
+
+ this.webClient.get().uri("/key").exchange().expectStatus().isOk().expectBody(String.class)
+ .value(Matchers.equalTo("from-configmap"));
+ }
+
+}
diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/resources/retryable-sources-order.yaml b/spring-cloud-kubernetes-fabric8-config/src/test/resources/retryable-sources-order.yaml
new file mode 100644
index 00000000..06afb7ca
--- /dev/null
+++ b/spring-cloud-kubernetes-fabric8-config/src/test/resources/retryable-sources-order.yaml
@@ -0,0 +1,17 @@
+spring:
+ application:
+ name: sources-order
+ cloud:
+ kubernetes:
+ secrets:
+ fail-fast: true
+ enabled: true
+ enableApi: true
+ namespace: spring-k8s
+ sources:
+ - name: my-secret
+ config:
+ fail-fast: true
+ namespace: spring-k8s
+ sources:
+ - name: my-configmap
diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/resources/sources-order.yaml b/spring-cloud-kubernetes-fabric8-config/src/test/resources/sources-order.yaml
new file mode 100644
index 00000000..c76cb733
--- /dev/null
+++ b/spring-cloud-kubernetes-fabric8-config/src/test/resources/sources-order.yaml
@@ -0,0 +1,15 @@
+spring:
+ application:
+ name: sources-order
+ cloud:
+ kubernetes:
+ secrets:
+ enabled: true
+ enableApi: true
+ namespace: spring-k8s
+ sources:
+ - name: my-secret
+ config:
+ namespace: spring-k8s
+ sources:
+ - name: my-configmap