diff --git a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigUtils.java b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigUtils.java index f18e1fc4..54525e87 100644 --- a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigUtils.java +++ b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigUtils.java @@ -16,6 +16,7 @@ package org.springframework.cloud.kubernetes.client.config; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -154,8 +155,8 @@ public final class KubernetesClientConfigUtils { * 4. gather all the names of the secrets + decoded data they hold * */ - static MultipleSourcesContainer secretsDataByName(CoreV1Api client, String namespace, Set sourceNames, - Environment environment) { + static MultipleSourcesContainer secretsDataByName(CoreV1Api client, String namespace, + LinkedHashSet sourceNames, Environment environment) { List secrets = secretsSearch(client, namespace); if (ConfigUtils.noSources(secrets, namespace)) { return MultipleSourcesContainer.empty(); @@ -174,8 +175,8 @@ public final class KubernetesClientConfigUtils { * 4. gather all the names of the config maps + data they hold * */ - static MultipleSourcesContainer configMapsDataByName(CoreV1Api client, String namespace, Set sourceNames, - Environment environment) { + static MultipleSourcesContainer configMapsDataByName(CoreV1Api client, String namespace, + LinkedHashSet sourceNames, Environment environment) { List configMaps = configMapsSearch(client, namespace); if (ConfigUtils.noSources(configMaps, namespace)) { return MultipleSourcesContainer.empty(); diff --git a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/NamedConfigMapContextToSourceDataProvider.java b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/NamedConfigMapContextToSourceDataProvider.java index 749e08a4..c19ee3fb 100644 --- a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/NamedConfigMapContextToSourceDataProvider.java +++ b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/NamedConfigMapContextToSourceDataProvider.java @@ -16,7 +16,7 @@ package org.springframework.cloud.kubernetes.client.config; -import java.util.Set; +import java.util.LinkedHashSet; import java.util.function.Supplier; import org.springframework.cloud.kubernetes.commons.config.MultipleSourcesContainer; @@ -43,7 +43,7 @@ final class NamedConfigMapContextToSourceDataProvider implements Supplier sourceNames) { + public MultipleSourcesContainer dataSupplier(LinkedHashSet sourceNames) { return KubernetesClientConfigUtils.configMapsDataByName(context.client(), context.namespace(), sourceNames, context.environment()); } diff --git a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/NamedSecretContextToSourceDataProvider.java b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/NamedSecretContextToSourceDataProvider.java index 12469c99..82eee371 100644 --- a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/NamedSecretContextToSourceDataProvider.java +++ b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/NamedSecretContextToSourceDataProvider.java @@ -16,7 +16,7 @@ package org.springframework.cloud.kubernetes.client.config; -import java.util.Set; +import java.util.LinkedHashSet; import java.util.function.Supplier; import org.springframework.cloud.kubernetes.commons.config.MultipleSourcesContainer; @@ -42,7 +42,7 @@ final class NamedSecretContextToSourceDataProvider implements Supplier sourceNames) { + public MultipleSourcesContainer dataSupplier(LinkedHashSet sourceNames) { return KubernetesClientConfigUtils.secretsDataByName(context.client(), context.namespace(), sourceNames, context.environment()); } diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/LabeledConfigMapWithProfileTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/LabeledConfigMapWithProfileTests.java index 3f625741..4a206dab 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/LabeledConfigMapWithProfileTests.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/LabeledConfigMapWithProfileTests.java @@ -61,15 +61,16 @@ abstract class LabeledConfigMapWithProfileTests { /** *
-	 *   this one is taken from : "green-configmap.green-configmap-k8s.green-configmap-prod".
-	 *   We find "green-configmap" by labels, also "green-configmap-k8s" and "green-configmap-prod" exists,
-	 *   because "includeProfileSpecificSources=true" is set.
+	 *   this one is taken from : ""green-configmap.green-configmap-k8s.green-configmap-prod.green-purple-configmap.green-purple-configmap-k8s"".
+	 *   We find "green-configmap" by labels, also "green-configmap-k8s", "green-configmap-prod" exists,
+	 *   because "includeProfileSpecificSources=true" is set. Also "green-purple-configmap" and "green-purple-configmap-k8s"
+	 *   are found.
 	 * 
*/ @Test void testGreen() { this.webClient.get().uri("/labeled-configmap/profile/green").exchange().expectStatus().isOk() - .expectBody(String.class).value(Matchers.equalTo("2#6#7")); + .expectBody(String.class).value(Matchers.equalTo("2#6#7#eight-ish")); } } diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/controller/LabeledConfigMapWithProfileController.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/controller/LabeledConfigMapWithProfileController.java index 33cc1d11..e81f6678 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/controller/LabeledConfigMapWithProfileController.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/controller/LabeledConfigMapWithProfileController.java @@ -40,7 +40,7 @@ public class LabeledConfigMapWithProfileController { @GetMapping("/labeled-configmap/profile/green") public String green() { - return green.getTwo() + "#" + green.getSix() + "#" + green.getSeven(); + return green.getTwo() + "#" + green.getSix() + "#" + green.getSeven() + "#" + green.getEight(); } } diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/properties/Green.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/properties/Green.java index 59bc8289..7e8807f4 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/properties/Green.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/properties/Green.java @@ -18,7 +18,7 @@ package org.springframework.cloud.kubernetes.client.config.applications.labeled_ import org.springframework.boot.context.properties.ConfigurationProperties; -@ConfigurationProperties("green-configmap.green-configmap-k8s.green-configmap-prod") +@ConfigurationProperties("green-configmap.green-configmap-k8s.green-configmap-prod.green-purple-configmap.green-purple-configmap-k8s") public class Green { private String two; @@ -27,6 +27,8 @@ public class Green { private String seven; + private String eight; + public String getTwo() { return two; } @@ -51,4 +53,12 @@ public class Green { this.seven = seven; } + public String getEight() { + return eight; + } + + public void setEight(String eight) { + this.eight = eight; + } + } diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/LabeledSecretWithProfileTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/LabeledSecretWithProfileTests.java index 6cf9ae37..fbcb546a 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/LabeledSecretWithProfileTests.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/LabeledSecretWithProfileTests.java @@ -73,15 +73,16 @@ abstract class LabeledSecretWithProfileTests { /** *
-	 *   this one is taken from : "green-secret.green-secret-k8s.green-secret-prod".
+	 *   this one is taken from : "green-purple-secret.green-purple-secret-k8s.green-secret.green-secret-k8s.green-secret-prod".
 	 *   We find "green-secret" by labels, also "green-secrets-k8s" and "green-secrets-prod" exists,
-	 *   because "includeProfileSpecificSources=true" is set.
+	 *   because "includeProfileSpecificSources=true" is set. Also "green-purple-secret" and "green-purple-secret-k8s"
+	 * 	 are found.
 	 * 
*/ @Test void testGreen() { this.webClient.get().uri("/labeled-secret/profile/green").exchange().expectStatus().isOk() - .expectBody(String.class).value(Matchers.equalTo("2#6#7")); + .expectBody(String.class).value(Matchers.equalTo("2#6#7#eight-ish")); } } diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/controller/LabeledSecretWithProfileController.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/controller/LabeledSecretWithProfileController.java index f342746d..97f6a06e 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/controller/LabeledSecretWithProfileController.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/controller/LabeledSecretWithProfileController.java @@ -40,7 +40,7 @@ public class LabeledSecretWithProfileController { @GetMapping("/labeled-secret/profile/green") public String green() { - return green.getTwo() + "#" + green.getSix() + "#" + green.getSeven(); + return green.getTwo() + "#" + green.getSix() + "#" + green.getSeven() + "#" + green.getEight(); } } diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/properties/Green.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/properties/Green.java index 04c7e5c5..851f229e 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/properties/Green.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/properties/Green.java @@ -18,7 +18,7 @@ package org.springframework.cloud.kubernetes.client.config.applications.labeled_ import org.springframework.boot.context.properties.ConfigurationProperties; -@ConfigurationProperties("green-secret.green-secret-k8s.green-secret-prod") +@ConfigurationProperties("green-purple-secret.green-purple-secret-k8s.green-secret.green-secret-k8s.green-secret-prod") public class Green { private String two; @@ -27,6 +27,8 @@ public class Green { private String seven; + private String eight; + public String getTwo() { return two; } @@ -51,4 +53,12 @@ public class Green { this.seven = seven; } + public String getEight() { + return eight; + } + + public void setEight(String eight) { + this.eight = eight; + } + } diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/LabeledConfigMapWithProfileConfigurationStub.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/LabeledConfigMapWithProfileConfigurationStub.java index 6feb5848..09205f36 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/LabeledConfigMapWithProfileConfigurationStub.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/LabeledConfigMapWithProfileConfigurationStub.java @@ -64,16 +64,21 @@ public class LabeledConfigMapWithProfileConfigurationStub { return apiClient; } - /* - *
 - configmap with name "color-configmap", with labels: "{color: blue}" and
-	 * "explicitPrefix: blue" - configmap with name "green-configmap", with labels:
-	 * "{color: green}" and "explicitPrefix: blue-again" - configmap with name
-	 * "red-configmap", with labels "{color: not-red}" and "useNameAsPrefix: true" -
-	 * configmap with name "yellow-configmap" with labels "{color: not-yellow}" and
-	 * useNameAsPrefix: true - configmap with name "color-configmap-k8s", with labels :
-	 * "{color: not-blue}" - configmap with name "green-configmap-k8s", with labels :
-	 * "{color: green-k8s}" - configmap with name "green-configmap-prod", with labels :
-	 * "{color: green-prod}" 
+ /** + *
+	 *     - configmap with name "color-configmap", with labels: "{color: blue}" and "explicitPrefix: blue"
+	 *     - configmap with name "green-configmap", with labels: "{color: green}" and "explicitPrefix: blue-again"
+	 *     - configmap with name "red-configmap", with labels "{color: not-red}" and "useNameAsPrefix: true"
+	 *     - configmap with name "yellow-configmap" with labels "{color: not-yellow}" and useNameAsPrefix: true
+	 *     - configmap with name "color-configmap-k8s", with labels : "{color: not-blue}"
+	 *     - configmap with name "green-configmap-k8s", with labels : "{color: green-k8s}"
+	 *     - configmap with name "green-configmap-prod", with labels : "{color: green-prod}"
+	 *
+	 *     # a test that proves order: first read non-profile based configmaps, thus profile based
+	 *     # configmaps override non-profile ones.
+	 *     - configmap with name "green-purple-configmap", labels "{color: green, shape: round}", data: "{eight: 8}"
+	 *     - configmap with name "green-purple-configmap-k8s", labels "{color: black}", data: "{eight: eight-ish}"
+	 * 
*/ public static void stubData() { @@ -118,6 +123,18 @@ public class LabeledConfigMapWithProfileConfigurationStub { .withLabels(Map.of("color", "yellow")).build()) .addToData(Collections.singletonMap("four", "4")).build(); + // is found by labels + V1ConfigMap greenPurpleConfigMap = new V1ConfigMapBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("green-purple-configmap").withNamespace("spring-k8s") + .withLabels(Map.of("color", "green", "shape", "round")).build()) + .addToData(Collections.singletonMap("eight", "8")).build(); + + // is taken and thus overrides the above + V1ConfigMap greenPurpleConfigMapK8s = new V1ConfigMapBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("green-purple-configmap-k8s") + .withNamespace("spring-k8s").withLabels(Map.of("color", "black")).build()) + .addToData(Collections.singletonMap("eight", "eight-ish")).build(); + // the actual stub for CoreV1Api calls V1ConfigMapList configMaps = new V1ConfigMapList(); configMaps.addItemsItem(colorConfigMap); @@ -127,6 +144,8 @@ public class LabeledConfigMapWithProfileConfigurationStub { configMaps.addItemsItem(greenConfigMapProd); configMaps.addItemsItem(redConfigMap); configMaps.addItemsItem(yellowConfigMap); + configMaps.addItemsItem(greenPurpleConfigMap); + configMaps.addItemsItem(greenPurpleConfigMapK8s); WireMock.stubFor(WireMock.get("/api/v1/namespaces/spring-k8s/configmaps") .willReturn(WireMock.aResponse().withStatus(200).withBody(new JSON().serialize(configMaps)))); diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/LabeledSecretWithProfileConfigurationStub.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/LabeledSecretWithProfileConfigurationStub.java index 8a7b98a7..813d37d5 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/LabeledSecretWithProfileConfigurationStub.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/LabeledSecretWithProfileConfigurationStub.java @@ -16,6 +16,7 @@ package org.springframework.cloud.kubernetes.client.config.boostrap.stubs; +import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.Map; @@ -64,15 +65,21 @@ public class LabeledSecretWithProfileConfigurationStub { return apiClient; } - /* - *
 - secret with name "color-secret", with labels: "{color: blue}" and
-	 * "explicitPrefix: blue" - secret with name "green-secret", with labels:
-	 * "{color: green}" and "explicitPrefix: blue-again" - secret with name "red-secret",
-	 * with labels "{color: not-red}" and "useNameAsPrefix: true" - secret with name
-	 * "yellow-secret" with labels "{color: not-yellow}" and useNameAsPrefix: true -
-	 * secret with name "color-secret-k8s", with labels : "{color: not-blue}" - secret
-	 * with name "green-secret-k8s", with labels : "{color: green-k8s}" - secret with name
-	 * "green-secret-prod", with labels : "{color: green-prod}" 
+ /** + *
+	 *     - secret with name "color-secret", with labels: "{color: blue}" and "explicitPrefix: blue"
+	 *     - secret with name "green-secret", with labels: "{color: green}" and "explicitPrefix: blue-again"
+	 *     - secret with name "red-secret", with labels "{color: not-red}" and "useNameAsPrefix: true"
+	 *     - secret with name "yellow-secret" with labels "{color: not-yellow}" and useNameAsPrefix: true
+	 *     - secret with name "color-secret-k8s", with labels : "{color: not-blue}"
+	 *     - secret with name "green-secret-k8s", with labels : "{color: green-k8s}"
+	 *     - secret with name "green-secret-prod", with labels : "{color: green-prod}"
+	 *
+	 *     # a test that proves order: first read non-profile based secrets, thus profile based
+	 *     # secrets override non-profile ones.
+	 *     - secret with name "green-purple-secret", labels "{color: green, shape: round}", data: "{eight: 8}"
+	 *     - secret with name "green-purple-secret-k8s", labels "{color: black}", data: "{eight: eight-ish}"
+	 * 
*/ public static void stubData() { @@ -80,42 +87,54 @@ public class LabeledSecretWithProfileConfigurationStub { V1Secret colorSecret = new V1SecretBuilder() .withMetadata(new V1ObjectMetaBuilder().withName("color-secret").withNamespace("spring-k8s") .withLabels(Map.of("color", "blue")).build()) - .addToData(Collections.singletonMap("one", "1".getBytes())).build(); + .addToData(Collections.singletonMap("one", "1".getBytes(StandardCharsets.UTF_8))).build(); // is not taken, since "profileSpecificSources=false" for the above V1Secret colorSecretK8s = new V1SecretBuilder() .withMetadata(new V1ObjectMetaBuilder().withName("color-secret-k8s").withNamespace("spring-k8s") .withLabels(Map.of("color", "not-blue")).build()) - .addToData(Collections.singletonMap("five", "5".getBytes())).build(); + .addToData(Collections.singletonMap("five", "5".getBytes(StandardCharsets.UTF_8))).build(); // is found by labels V1Secret greenSecret = new V1SecretBuilder() .withMetadata(new V1ObjectMetaBuilder().withName("green-secret").withNamespace("spring-k8s") .withLabels(Map.of("color", "green")).build()) - .addToData(Collections.singletonMap("two", "2".getBytes())).build(); + .addToData(Collections.singletonMap("two", "2".getBytes(StandardCharsets.UTF_8))).build(); V1Secret greenSecretK8s = new V1SecretBuilder() .withMetadata(new V1ObjectMetaBuilder().withName("green-secret-k8s").withNamespace("spring-k8s") .withLabels(Map.of("color", "green-k8s")).build()) - .addToData(Collections.singletonMap("six", "6".getBytes())).build(); + .addToData(Collections.singletonMap("six", "6".getBytes(StandardCharsets.UTF_8))).build(); // is taken because prod profile is active and "profileSpecificSources=true" V1Secret shapeSecretProd = new V1SecretBuilder() .withMetadata(new V1ObjectMetaBuilder().withName("green-secret-prod").withNamespace("spring-k8s") .withLabels(Map.of("color", "green-prod")).build()) - .addToData(Collections.singletonMap("seven", "7".getBytes())).build(); + .addToData(Collections.singletonMap("seven", "7".getBytes(StandardCharsets.UTF_8))).build(); // not taken V1Secret redSecret = new V1SecretBuilder() .withMetadata(new V1ObjectMetaBuilder().withName("red-secret").withNamespace("spring-k8s") .withLabels(Map.of("color", "not-red")).build()) - .addToData(Collections.singletonMap("three", "3".getBytes())).build(); + .addToData(Collections.singletonMap("three", "3".getBytes(StandardCharsets.UTF_8))).build(); // not taken V1Secret yellowSecret = new V1SecretBuilder() .withMetadata(new V1ObjectMetaBuilder().withName("yellow-secret").withNamespace("spring-k8s") .withLabels(Map.of("color", "not-yellow")).build()) - .addToData(Collections.singletonMap("four", "4".getBytes())).build(); + .addToData(Collections.singletonMap("four", "4".getBytes(StandardCharsets.UTF_8))).build(); + + // is found by labels + V1Secret greenPurpleSecret = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("green-purple-secret").withNamespace("spring-k8s") + .withLabels(Map.of("color", "green", "shape", "round")).build()) + .addToData(Collections.singletonMap("eight", "8".getBytes(StandardCharsets.UTF_8))).build(); + + // is taken and thus overrides the above + V1Secret greenPurpleSecretK8s = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("green-purple-secret-k8s").withNamespace("spring-k8s") + .withLabels(Map.of("color", "black")).build()) + .addToData(Collections.singletonMap("eight", "eight-ish".getBytes(StandardCharsets.UTF_8))).build(); // the actual stub for CoreV1Api calls V1SecretList secrets = new V1SecretList(); @@ -126,6 +145,8 @@ public class LabeledSecretWithProfileConfigurationStub { secrets.addItemsItem(shapeSecretProd); secrets.addItemsItem(redSecret); secrets.addItemsItem(yellowSecret); + secrets.addItemsItem(greenPurpleSecret); + secrets.addItemsItem(greenPurpleSecretK8s); WireMock.stubFor(WireMock.get("/api/v1/namespaces/spring-k8s/secrets") .willReturn(WireMock.aResponse().withStatus(200).withBody(new JSON().serialize(secrets)))); diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/NamedConfigMapWithProfileConfigurationStub.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/NamedConfigMapWithProfileConfigurationStub.java index ea83ae13..8e2147c9 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/NamedConfigMapWithProfileConfigurationStub.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/NamedConfigMapWithProfileConfigurationStub.java @@ -66,6 +66,11 @@ public class NamedConfigMapWithProfileConfigurationStub { public static void stubData() { + // "one" and "oneFromK8s" also prove the fact that the right order is preserved: + // first non-profile based + // and only after profile based sources. Thus, properties from "one" are + // overridden by the ones from "oneFromK8s". + // We have a test that asserts this. V1ConfigMap one = new V1ConfigMapBuilder() .withMetadata(new V1ObjectMetaBuilder().withName("configmap-one").withNamespace("spring-k8s").build()) .addToData(Collections.singletonMap("one.property", "one")).build(); diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/NamedSecretWithProfileConfigurationStub.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/NamedSecretWithProfileConfigurationStub.java index 6031358a..b1e1a195 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/NamedSecretWithProfileConfigurationStub.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/NamedSecretWithProfileConfigurationStub.java @@ -64,6 +64,12 @@ public class NamedSecretWithProfileConfigurationStub { } public static void stubData() { + + // "one" and "oneWithProfile" also prove the fact that the right order is + // preserved: first non-profile based + // and only after profile based sources. Thus, properties from "one" are + // overridden by the ones from "oneWithProfile". + // We have a test that asserts this. V1Secret one = new V1SecretBuilder() .withMetadata(new V1ObjectMetaBuilder().withName("secret-one").withNamespace("spring-k8s") .withResourceVersion("1").build()) diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ConfigUtils.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ConfigUtils.java index fe67bea6..c726d7c7 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ConfigUtils.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ConfigUtils.java @@ -19,11 +19,11 @@ package org.springframework.cloud.kubernetes.commons.config; import java.util.ArrayList; import java.util.Base64; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -165,25 +165,32 @@ public final class ConfigUtils { * defined order). */ public static MultipleSourcesContainer processNamedData(List strippedSources, - Environment environment, Set sourceNames, String namespace, boolean decode) { + Environment environment, LinkedHashSet sourceNames, String namespace, boolean decode) { - Set foundSourceNames = new HashSet<>(); + Map hashByName = strippedSources.stream() + .collect(Collectors.toMap(StrippedSourceContainer::name, Function.identity())); + + LinkedHashSet foundSourceNames = new LinkedHashSet<>(); Map data = new HashMap<>(); - strippedSources.stream().filter(source -> sourceNames.contains(source.name())).collect(Collectors.toList()) - .forEach(foundSource -> { - String sourceName = foundSource.name(); - LOG.debug("Found source with name : '" + sourceName + " in namespace: '" + namespace + "'"); - foundSourceNames.add(sourceName); - // see if data is a single yaml/properties file and if it needs - // decoding - Map rawData = foundSource.data(); - if (decode) { - rawData = decodeData(rawData); - } - data.putAll(SourceDataEntriesProcessor.processAllEntries(rawData == null ? Map.of() : rawData, - environment)); - }); + // this is an ordered stream, and it means that non-profile based sources will be + // processed before profile based sources. This way, we replicate that + // "application-dev.yaml" + // overrides properties from "application.yaml" + sourceNames.forEach(source -> { + StrippedSourceContainer stripped = hashByName.get(source); + if (stripped != null) { + LOG.debug("Found source with name : '" + source + " in namespace: '" + namespace + "'"); + foundSourceNames.add(source); + // see if data is a single yaml/properties file and if it needs decoding + Map rawData = stripped.data(); + if (decode) { + rawData = decodeData(rawData); + } + data.putAll(SourceDataEntriesProcessor.processAllEntries(rawData == null ? Map.of() : rawData, + environment)); + } + }); return new MultipleSourcesContainer(foundSourceNames, data); } @@ -200,16 +207,16 @@ public final class ConfigUtils { boolean decode) { // find sources by provided labels - List sourcesByLabels = containers.stream().filter(one -> { + List byLabels = containers.stream().filter(one -> { Map sourceLabels = one.labels(); Map labelsToSearchAgainst = sourceLabels == null ? Map.of() : sourceLabels; return labelsToSearchAgainst.entrySet().containsAll((labels.entrySet())); }).collect(Collectors.toList()); - // compute profile based sources (based on the ones we found by labels) + // compute profile based source names (based on the ones we found by labels) List sourceNamesByLabelsWithProfile = new ArrayList<>(); if (profiles != null && !profiles.isEmpty()) { - for (StrippedSourceContainer one : sourcesByLabels) { + for (StrippedSourceContainer one : byLabels) { for (String profile : profiles) { String name = one.name() + "-" + profile; sourceNamesByLabelsWithProfile.add(name); @@ -220,15 +227,18 @@ public final class ConfigUtils { // once we know sources by labels (and thus their names), we can find out // profiles based sources from the above. This would get all sources // we are interested in. - List sourcesToTake = containers.stream() - .filter(one -> sourceNamesByLabelsWithProfile.contains(one.name())) - .collect(Collectors.toCollection(ArrayList::new)); - sourcesToTake.addAll(sourcesByLabels); + List byProfile = containers.stream() + .filter(one -> sourceNamesByLabelsWithProfile.contains(one.name())).collect(Collectors.toList()); - Set sourceNames = new HashSet<>(); + // this makes sure that we first have "app" and then "app-dev" in the list + List all = new ArrayList<>(byLabels.size() + byProfile.size()); + all.addAll(byLabels); + all.addAll(byProfile); + + LinkedHashSet sourceNames = new LinkedHashSet<>(); Map result = new HashMap<>(); - sourcesToTake.forEach(source -> { + all.forEach(source -> { String foundSourceName = source.name(); LOG.debug("Loaded source with name : '" + foundSourceName + " in namespace: '" + namespace + "'"); sourceNames.add(foundSourceName); diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/MultipleSourcesContainer.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/MultipleSourcesContainer.java index f96661d4..f8a39c91 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/MultipleSourcesContainer.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/MultipleSourcesContainer.java @@ -16,18 +16,19 @@ package org.springframework.cloud.kubernetes.commons.config; +import java.util.LinkedHashSet; import java.util.Map; -import java.util.Set; /** * @author wind57 * * Container that stores multiple sources, to be exact their names and their flattenned - * data. + * data. We force a LinkedHashSet on purpose, to preserve the order of sources. */ -public final record MultipleSourcesContainer(Set names, Map data) { +public final record MultipleSourcesContainer(LinkedHashSet names, Map data) { - private static final MultipleSourcesContainer EMPTY = new MultipleSourcesContainer(Set.of(), Map.of()); + private static final MultipleSourcesContainer EMPTY = new MultipleSourcesContainer(new LinkedHashSet<>(0), + Map.of()); public static MultipleSourcesContainer empty() { return EMPTY; diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NamedSourceData.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NamedSourceData.java index 037c36ca..fe7b12bf 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NamedSourceData.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NamedSourceData.java @@ -16,9 +16,8 @@ package org.springframework.cloud.kubernetes.commons.config; -import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.Map; -import java.util.Set; import java.util.stream.Collectors; import static org.springframework.cloud.kubernetes.commons.config.ConfigUtils.onException; @@ -32,27 +31,27 @@ import static org.springframework.cloud.kubernetes.commons.config.Constants.PROP */ public abstract class NamedSourceData { - public final SourceData compute(String initialSourceName, ConfigUtils.Prefix prefix, String target, - boolean profileSources, boolean failFast, String namespace, String[] activeProfiles) { + public final SourceData compute(String sourceName, ConfigUtils.Prefix prefix, String target, boolean profileSources, + boolean failFast, String namespace, String[] activeProfiles) { - Set sourceNames = new HashSet<>(); - sourceNames.add(initialSourceName); + LinkedHashSet sourceNames = new LinkedHashSet<>(); + // first comes non-profile based source + sourceNames.add(sourceName); MultipleSourcesContainer data = MultipleSourcesContainer.empty(); - String currentSourceName; try { if (profileSources) { for (String activeProfile : activeProfiles) { - currentSourceName = initialSourceName + "-" + activeProfile; - sourceNames.add(currentSourceName); + // add all profile based sources _after_ non-profile based one + sourceNames.add(sourceName + "-" + activeProfile); } } data = dataSupplier(sourceNames); if (data.names().isEmpty()) { - return new SourceData(ConfigUtils.sourceName(target, initialSourceName, namespace), Map.of()); + return new SourceData(ConfigUtils.sourceName(target, sourceName, namespace), Map.of()); } if (prefix != ConfigUtils.Prefix.DEFAULT) { @@ -74,9 +73,10 @@ public abstract class NamedSourceData { /** * Implementation specific (fabric8 or k8s-native) way to get the data from then given * source names. - * @param sourceNames the ones that have been configured + * @param sourceNames the ones that have been configured, LinkedHashSet in order ot + * preserve the order: non-profile source first and then the rest * @return an Entry that holds the names of the source that were found and their data */ - public abstract MultipleSourcesContainer dataSupplier(Set sourceNames); + public abstract MultipleSourcesContainer dataSupplier(LinkedHashSet sourceNames); } diff --git a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigUtils.java b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigUtils.java index d319e78d..3afed478 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigUtils.java +++ b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigUtils.java @@ -16,6 +16,7 @@ package org.springframework.cloud.kubernetes.fabric8.config; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -161,7 +162,7 @@ public final class Fabric8ConfigUtils { * */ static MultipleSourcesContainer secretsDataByName(KubernetesClient client, String namespace, - Set sourceNames, Environment environment) { + LinkedHashSet sourceNames, Environment environment) { List secrets = secretsSearch(client, namespace); if (ConfigUtils.noSources(secrets, namespace)) { return MultipleSourcesContainer.empty(); @@ -181,7 +182,7 @@ public final class Fabric8ConfigUtils { * */ static MultipleSourcesContainer configMapsDataByName(KubernetesClient client, String namespace, - Set sourceNames, Environment environment) { + LinkedHashSet sourceNames, Environment environment) { List configMaps = configMapsSearch(client, namespace); if (ConfigUtils.noSources(configMaps, namespace)) { return MultipleSourcesContainer.empty(); diff --git a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/NamedConfigMapContextToSourceDataProvider.java b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/NamedConfigMapContextToSourceDataProvider.java index a1c6e4e2..d1785c19 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/NamedConfigMapContextToSourceDataProvider.java +++ b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/NamedConfigMapContextToSourceDataProvider.java @@ -16,7 +16,7 @@ package org.springframework.cloud.kubernetes.fabric8.config; -import java.util.Set; +import java.util.LinkedHashSet; import java.util.function.Supplier; import org.springframework.cloud.kubernetes.commons.config.MultipleSourcesContainer; @@ -50,7 +50,7 @@ final class NamedConfigMapContextToSourceDataProvider implements Supplier sourceNames) { + public MultipleSourcesContainer dataSupplier(LinkedHashSet sourceNames) { return Fabric8ConfigUtils.configMapsDataByName(context.client(), context.namespace(), sourceNames, context.environment()); } diff --git a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/NamedSecretContextToSourceDataProvider.java b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/NamedSecretContextToSourceDataProvider.java index 2d3d793c..f89303f0 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/NamedSecretContextToSourceDataProvider.java +++ b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/NamedSecretContextToSourceDataProvider.java @@ -16,7 +16,7 @@ package org.springframework.cloud.kubernetes.fabric8.config; -import java.util.Set; +import java.util.LinkedHashSet; import java.util.function.Supplier; import org.springframework.cloud.kubernetes.commons.config.MultipleSourcesContainer; @@ -41,7 +41,7 @@ final class NamedSecretContextToSourceDataProvider implements Supplier sourceNames) { + public MultipleSourcesContainer dataSupplier(LinkedHashSet sourceNames) { return Fabric8ConfigUtils.secretsDataByName(context.client(), context.namespace(), sourceNames, context.environment()); } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigUtilsTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigUtilsTests.java index c8207272..e6dd2417 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigUtilsTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigUtilsTests.java @@ -17,6 +17,7 @@ package org.springframework.cloud.kubernetes.fabric8.config; import java.util.Base64; +import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; @@ -211,7 +212,9 @@ class Fabric8ConfigUtilsTests { void testSecretDataByNameSecretNotFound() { client.secrets().inNamespace("spring-k8s").create( new SecretBuilder().withMetadata(new ObjectMetaBuilder().withName("my-secret").build()).build()); - MultipleSourcesContainer result = Fabric8ConfigUtils.secretsDataByName(client, "spring-k8s", Set.of("nope"), + LinkedHashSet names = new LinkedHashSet<>(); + names.add("nope"); + MultipleSourcesContainer result = Fabric8ConfigUtils.secretsDataByName(client, "spring-k8s", names, new MockEnvironment()); Assertions.assertEquals(0, result.names().size()); Assertions.assertEquals(0, result.data().size()); @@ -223,9 +226,11 @@ class Fabric8ConfigUtilsTests { client.secrets().inNamespace("spring-k8s") .create(new SecretBuilder().withMetadata(new ObjectMetaBuilder().withName("my-secret").build()) .addToData(Map.of("property", Base64.getEncoder().encodeToString("value".getBytes()))).build()); + LinkedHashSet names = new LinkedHashSet<>(); + names.add("my-secret"); - MultipleSourcesContainer result = Fabric8ConfigUtils.secretsDataByName(client, "spring-k8s", - Set.of("my-secret"), new MockEnvironment()); + MultipleSourcesContainer result = Fabric8ConfigUtils.secretsDataByName(client, "spring-k8s", names, + new MockEnvironment()); Assertions.assertEquals(1, result.names().size()); Assertions.assertEquals("value", result.data().get("property")); } @@ -242,9 +247,12 @@ class Fabric8ConfigUtilsTests { .create(new SecretBuilder().withMetadata(new ObjectMetaBuilder().withName("my-secret-2").build()) .addToData(Map.of("property-2", Base64.getEncoder().encodeToString("value-2".getBytes()))) .build()); + LinkedHashSet names = new LinkedHashSet<>(); + names.add("my-secret"); + names.add("my-secret-2"); - MultipleSourcesContainer result = Fabric8ConfigUtils.secretsDataByName(client, "spring-k8s", - Set.of("my-secret", "my-secret-2"), new MockEnvironment()); + MultipleSourcesContainer result = Fabric8ConfigUtils.secretsDataByName(client, "spring-k8s", names, + new MockEnvironment()); Assertions.assertTrue(result.names().contains("my-secret")); Assertions.assertTrue(result.names().contains("my-secret-2")); @@ -259,8 +267,11 @@ class Fabric8ConfigUtilsTests { void testConfigMapsDataByNameFoundNoData() { client.configMaps().inNamespace("spring-k8s").create( new ConfigMapBuilder().withMetadata(new ObjectMetaBuilder().withName("my-config-map").build()).build()); - MultipleSourcesContainer result = Fabric8ConfigUtils.configMapsDataByName(client, "spring-k8s", - Set.of("my-config-map"), new MockEnvironment()); + LinkedHashSet names = new LinkedHashSet<>(); + names.add("my-config-map"); + + MultipleSourcesContainer result = Fabric8ConfigUtils.configMapsDataByName(client, "spring-k8s", names, + new MockEnvironment()); Assertions.assertEquals(Set.of("my-config-map"), result.names()); Assertions.assertTrue(result.data().isEmpty()); } @@ -270,8 +281,10 @@ class Fabric8ConfigUtilsTests { void testConfigMapsDataByNameNotFound() { client.configMaps().inNamespace("spring-k8s").create( new ConfigMapBuilder().withMetadata(new ObjectMetaBuilder().withName("my-config-map").build()).build()); - MultipleSourcesContainer result = Fabric8ConfigUtils.configMapsDataByName(client, "spring-k8s", - Set.of("my-config-map-not-found"), new MockEnvironment()); + LinkedHashSet names = new LinkedHashSet<>(); + names.add("my-config-map-not-found"); + MultipleSourcesContainer result = Fabric8ConfigUtils.configMapsDataByName(client, "spring-k8s", names, + new MockEnvironment()); Assertions.assertEquals(Set.of(), result.names()); Assertions.assertTrue(result.data().isEmpty()); } @@ -283,8 +296,11 @@ class Fabric8ConfigUtilsTests { .create(new ConfigMapBuilder().withMetadata(new ObjectMetaBuilder().withName("my-config-map").build()) .addToData(Map.of("property", "value")).build()); - MultipleSourcesContainer result = Fabric8ConfigUtils.configMapsDataByName(client, "spring-k8s", - Set.of("my-config-map"), new MockEnvironment()); + LinkedHashSet names = new LinkedHashSet<>(); + names.add("my-config-map"); + + MultipleSourcesContainer result = Fabric8ConfigUtils.configMapsDataByName(client, "spring-k8s", names, + new MockEnvironment()); Assertions.assertEquals(Set.of("my-config-map"), result.names()); Assertions.assertEquals(Map.of("property", "value"), result.data()); } @@ -297,8 +313,11 @@ class Fabric8ConfigUtilsTests { .create(new ConfigMapBuilder().withMetadata(new ObjectMetaBuilder().withName("my-config-map").build()) .addToData(Map.of("application.yaml", "key1: value1")).build()); - MultipleSourcesContainer result = Fabric8ConfigUtils.configMapsDataByName(client, "spring-k8s", - Set.of("my-config-map"), new MockEnvironment()); + LinkedHashSet names = new LinkedHashSet<>(); + names.add("my-config-map"); + + MultipleSourcesContainer result = Fabric8ConfigUtils.configMapsDataByName(client, "spring-k8s", names, + new MockEnvironment()); Assertions.assertEquals(Set.of("my-config-map"), result.names()); Assertions.assertEquals(Map.of("key1", "value1"), result.data()); } @@ -315,8 +334,12 @@ class Fabric8ConfigUtilsTests { .create(new ConfigMapBuilder().withMetadata(new ObjectMetaBuilder().withName("my-config-map-2").build()) .addToData(Map.of("property-2", "value-2")).build()); - MultipleSourcesContainer result = Fabric8ConfigUtils.configMapsDataByName(client, "spring-k8s", - Set.of("my-config-map", "my-config-map-2"), new MockEnvironment()); + LinkedHashSet names = new LinkedHashSet<>(); + names.add("my-config-map"); + names.add("my-config-map-2"); + + MultipleSourcesContainer result = Fabric8ConfigUtils.configMapsDataByName(client, "spring-k8s", names, + new MockEnvironment()); Assertions.assertTrue(result.names().contains("my-config-map")); Assertions.assertTrue(result.names().contains("my-config-map-2")); diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/LabeledConfigMapWithProfileTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/LabeledConfigMapWithProfileTests.java index a3915d0c..aba44f2d 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/LabeledConfigMapWithProfileTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/LabeledConfigMapWithProfileTests.java @@ -47,6 +47,11 @@ abstract class LabeledConfigMapWithProfileTests { * - configmap with name "color-configmap-k8s", with labels : "{color: not-blue}" * - configmap with name "green-configmap-k8s", with labels : "{color: green-k8s}" * - configmap with name "green-configmap-prod", with labels : "{color: green-prod}" + * + * # a test that proves order: first read non-profile based configmaps, thus profile based + * # configmaps override non-profile ones. + * - configmap with name "green-purple-configmap", labels "{color: green, shape: round}", data: "{eight: 8}" + * - configmap with name "green-purple-configmap-k8s", labels "{color: black}", data: "{eight: eight-ish}" * */ static void setUpBeforeClass(KubernetesClient mockClient) { @@ -72,12 +77,12 @@ abstract class LabeledConfigMapWithProfileTests { createConfigMap("green-configmap", greenConfigMap, Collections.singletonMap("color", "green")); // is taken because k8s profile is active and "profileSpecificSources=true" - Map shapeConfigMapK8s = Collections.singletonMap("six", "6"); - createConfigMap("green-configmap-k8s", shapeConfigMapK8s, Collections.singletonMap("color", "green-k8s")); + Map greenConfigMapK8s = Collections.singletonMap("six", "6"); + createConfigMap("green-configmap-k8s", greenConfigMapK8s, Collections.singletonMap("color", "green-k8s")); // is taken because prod profile is active and "profileSpecificSources=true" - Map shapeConfigMapProd = Collections.singletonMap("seven", "7"); - createConfigMap("green-configmap-prod", shapeConfigMapProd, Collections.singletonMap("color", "green-prod")); + Map greenConfigMapProd = Collections.singletonMap("seven", "7"); + createConfigMap("green-configmap-prod", greenConfigMapProd, Collections.singletonMap("color", "green-prod")); // not taken Map redConfigMap = Collections.singletonMap("three", "3"); @@ -87,6 +92,14 @@ abstract class LabeledConfigMapWithProfileTests { Map yellowConfigMap = Collections.singletonMap("four", "4"); createConfigMap("yellow-configmap", yellowConfigMap, Collections.singletonMap("color", "not-yellow")); + // is found by labels + Map greenPurple = Collections.singletonMap("eight", "8"); + createConfigMap("green-purple-configmap", greenPurple, Map.of("color", "green", "shape", "round")); + + // is taken and thus overrides the above + Map greenPurpleK8s = Collections.singletonMap("eight", "eight-ish"); + createConfigMap("green-purple-configmap-k8s", greenPurpleK8s, Map.of("color", "black")); + } private static void createConfigMap(String name, Map data, Map labels) { @@ -109,15 +122,16 @@ abstract class LabeledConfigMapWithProfileTests { /** *
-	 *   this one is taken from : "green-configmap.green-configmap-k8s.green-configmap-prod".
+	 *   this one is taken from : "green-configmap.green-configmap-k8s.green-configmap-prod.green-purple-configmap.green-purple-configmap-k8s".
 	 *   We find "green-configmap" by labels, also "green-configmap-k8s" and "green-configmap-prod" exists,
-	 *   because "includeProfileSpecificSources=true" is set.
+	 *   because "includeProfileSpecificSources=true" is set. Also "green-purple-configmap" and "green-purple-configmap-k8s"
+	 * 	 are found.
 	 * 
*/ @Test void testGreen() { this.webClient.get().uri("/labeled-configmap/profile/green").exchange().expectStatus().isOk() - .expectBody(String.class).value(Matchers.equalTo("2#6#7")); + .expectBody(String.class).value(Matchers.equalTo("2#6#7#eight-ish")); } } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/controller/LabeledConfigMapWithProfileController.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/controller/LabeledConfigMapWithProfileController.java index 6f9818d1..ecaf20c6 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/controller/LabeledConfigMapWithProfileController.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/controller/LabeledConfigMapWithProfileController.java @@ -40,7 +40,7 @@ public class LabeledConfigMapWithProfileController { @GetMapping("/labeled-configmap/profile/green") public String green() { - return green.getTwo() + "#" + green.getSix() + "#" + green.getSeven(); + return green.getTwo() + "#" + green.getSix() + "#" + green.getSeven() + "#" + green.getEight(); } } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/properties/Green.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/properties/Green.java index 0c34cbe1..dd5b11c7 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/properties/Green.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/properties/Green.java @@ -18,7 +18,7 @@ package org.springframework.cloud.kubernetes.fabric8.config.labeled_config_map_w import org.springframework.boot.context.properties.ConfigurationProperties; -@ConfigurationProperties("green-configmap.green-configmap-k8s.green-configmap-prod") +@ConfigurationProperties("green-configmap.green-configmap-k8s.green-configmap-prod.green-purple-configmap.green-purple-configmap-k8s") public class Green { private String two; @@ -27,6 +27,8 @@ public class Green { private String seven; + private String eight; + public String getTwo() { return two; } @@ -51,4 +53,12 @@ public class Green { this.seven = seven; } + public String getEight() { + return eight; + } + + public void setEight(String eight) { + this.eight = eight; + } + } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/LabeledSecretWithProfileTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/LabeledSecretWithProfileTests.java index f2b8a22c..114644b7 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/LabeledSecretWithProfileTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/LabeledSecretWithProfileTests.java @@ -40,15 +40,21 @@ abstract class LabeledSecretWithProfileTests { @Autowired private WebTestClient webClient; - /* - *
 - secret with name "color-secret", with labels: "{color: blue}" and
-	 * "explicitPrefix: blue" - secret with name "green-secret", with labels:
-	 * "{color: green}" and "explicitPrefix: blue-again" - secret with name "red-secret",
-	 * with labels "{color: not-red}" and "useNameAsPrefix: true" - secret with name
-	 * "yellow-secret" with labels "{color: not-yellow}" and useNameAsPrefix: true -
-	 * secret with name "color-secret-k8s", with labels : "{color: not-blue}" - secret
-	 * with name "green-secret-k8s", with labels : "{color: green-k8s}" - secret with name
-	 * "green-secret-prod", with labels : "{color: green-prod}" 
+ /** + *
+	 *     - secret with name "color-secret", with labels: "{color: blue}" and "explicitPrefix: blue"
+	 *     - secret with name "green-secret", with labels: "{color: green}" and "explicitPrefix: blue-again"
+	 *     - secret with name "red-secret", with labels "{color: not-red}" and "useNameAsPrefix: true"
+	 *     - secret with name "yellow-secret" with labels "{color: not-yellow}" and useNameAsPrefix: true
+	 *     - secret with name "color-secret-k8s", with labels : "{color: not-blue}"
+	 *     - secret with name "green-secret-k8s", with labels : "{color: green-k8s}"
+	 *     - secret with name "green-secret-prod", with labels : "{color: green-prod}"
+	 *
+	 *     # a test that proves order: first read non-profile based secrets, thus profile based
+	 *     # secrets override non-profile ones.
+	 *     - secret with name "green-purple-secret", labels "{color: green, shape: round}", data: "{eight: 8}"
+	 *     - secret with name "green-purple-secret-k8s", labels "{color: black}", data: "{eight: eight-ish}"
+	 * 
*/ static void setUpBeforeClass(KubernetesClient mockClient) { LabeledSecretWithProfileTests.mockClient = mockClient; @@ -95,6 +101,16 @@ abstract class LabeledSecretWithProfileTests { Base64.getEncoder().encodeToString("4".getBytes(StandardCharsets.UTF_8))); createSecret("yellow-secret", yellowSecret, Collections.singletonMap("color", "not-yellow")); + // is found by labels + Map greenPurple = Collections.singletonMap("eight", + Base64.getEncoder().encodeToString("8".getBytes(StandardCharsets.UTF_8))); + createSecret("green-purple-secret", greenPurple, Map.of("color", "green", "shape", "round")); + + // is taken and thus overrides the above + Map greenPurpleK8s = Collections.singletonMap("eight", + Base64.getEncoder().encodeToString("eight-ish".getBytes(StandardCharsets.UTF_8))); + createSecret("green-purple-secret-k8s", greenPurpleK8s, Map.of("color", "black")); + } private static void createSecret(String name, Map data, Map labels) { @@ -117,15 +133,16 @@ abstract class LabeledSecretWithProfileTests { /** *
-	 *   this one is taken from : "green-secret.green-secret-k8s.green-secret-prod".
+	 *   this one is taken from : "green-purple-secret.green-purple-secret-k8s.green-secret.green-secret-k8s.green-secret-prod".
 	 *   We find "green-secret" by labels, also "green-secrets-k8s" and "green-secrets-prod" exists,
-	 *   because "includeProfileSpecificSources=true" is set.
+	 *   because "includeProfileSpecificSources=true" is set. Also "green-purple-secret" and "green-purple-secret-k8s"
+	 * 	 are found.
 	 * 
*/ @Test void testGreen() { this.webClient.get().uri("/labeled-secret/profile/green").exchange().expectStatus().isOk() - .expectBody(String.class).value(Matchers.equalTo("2#6#7")); + .expectBody(String.class).value(Matchers.equalTo("2#6#7#eight-ish")); } } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/controller/LabeledSecretWithProfileController.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/controller/LabeledSecretWithProfileController.java index 8464aec5..f4d548c7 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/controller/LabeledSecretWithProfileController.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/controller/LabeledSecretWithProfileController.java @@ -40,7 +40,7 @@ public class LabeledSecretWithProfileController { @GetMapping("/labeled-secret/profile/green") public String green() { - return green.getTwo() + "#" + green.getSix() + "#" + green.getSeven(); + return green.getTwo() + "#" + green.getSix() + "#" + green.getSeven() + "#" + green.getEight(); } } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/properties/Green.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/properties/Green.java index 1d08459d..4c0118fb 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/properties/Green.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/properties/Green.java @@ -18,7 +18,7 @@ package org.springframework.cloud.kubernetes.fabric8.config.labeled_secret_with_ import org.springframework.boot.context.properties.ConfigurationProperties; -@ConfigurationProperties("green-secret.green-secret-k8s.green-secret-prod") +@ConfigurationProperties("green-purple-secret.green-purple-secret-k8s.green-secret.green-secret-k8s.green-secret-prod") public class Green { private String two; @@ -27,6 +27,8 @@ public class Green { private String seven; + private String eight; + public String getTwo() { return two; } @@ -51,4 +53,12 @@ public class Green { this.seven = seven; } + public String getEight() { + return eight; + } + + public void setEight(String eight) { + this.eight = eight; + } + } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/NamedConfigMapWithProfileTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/NamedConfigMapWithProfileTests.java index ec19f47f..58e616f2 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/NamedConfigMapWithProfileTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/NamedConfigMapWithProfileTests.java @@ -48,6 +48,9 @@ abstract class NamedConfigMapWithProfileTests { System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, "test"); System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true"); + // the fact that property names are the same, also tests that we evaluate config + // map + // properties in order: first non-profile ones and then profile based one Map one = Collections.singletonMap("one.property", "one"); Map oneFromKubernetesProfile = Collections.singletonMap("one.property", "one-from-k8s"); diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/NamedSecretWithProfileTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/NamedSecretWithProfileTests.java index 9c0e8a21..d924fe6c 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/NamedSecretWithProfileTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/NamedSecretWithProfileTests.java @@ -50,6 +50,9 @@ abstract class NamedSecretWithProfileTests { System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, "test"); System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true"); + // both "secret-one" and "secret-one-k8s" have the same property "one.property", + // but since non-profile based sources are used before profile based sources, + // properties from secret "secret-one-k8s" must be visible in our tests. Map one = Collections.singletonMap("one.property", Base64.getEncoder().encodeToString("one".getBytes(StandardCharsets.UTF_8))); Map oneFromKubernetesProfile = Collections.singletonMap("one.property",