From cb3d7ca78100b0dcdcbb50f26d3ebdfb8613301b Mon Sep 17 00:00:00 2001 From: Ryan Baxter Date: Thu, 28 Mar 2024 09:59:29 -0400 Subject: [PATCH] Config Server Controller Should Return All PropertySources (#1600) --- .../config/KubernetesClientConfigContext.java | 7 +- .../KubernetesClientConfigMapsCache.java | 2 +- .../config/KubernetesClientConfigUtils.java | 20 ++- ...dConfigMapContextToSourceDataProvider.java | 12 +- ...amedSecretContextToSourceDataProvider.java | 12 +- ...igMapContextToSourceDataProviderTests.java | 10 +- ...ecretContextToSourceDataProviderTests.java | 10 +- .../commons/config/ConfigUtils.java | 42 +++++- .../NamedConfigMapNormalizedSource.java | 23 +++- .../config/NamedSecretNormalizedSource.java | 22 ++- .../commons/config/NamedSourceData.java | 6 +- .../config/SourceDataEntriesProcessor.java | 65 ++++++--- ...SourceDataEntriesProcessorSortedTests.java | 18 +++ ...bernetesConfigServerAutoConfiguration.java | 10 +- .../KubernetesConfigServerEnvironment.java | 31 +++++ .../KubernetesEnvironmentRepository.java | 54 ++++++-- .../KubernetesEnvironmentRepositoryTests.java | 129 ++++++++++++++---- ...KubernetesPropertySourceSupplierTests.java | 2 +- .../it/ConfigServerIntegrationTest.java | 40 +++++- ...dConfigMapContextToSourceDataProvider.java | 11 ++ ...amedSecretContextToSourceDataProvider.java | 10 ++ ...igMapContextToSourceDataProviderTests.java | 5 +- ...ecretContextToSourceDataProviderTests.java | 5 +- 23 files changed, 443 insertions(+), 103 deletions(-) create mode 100644 spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesConfigServerEnvironment.java diff --git a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigContext.java b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigContext.java index 91a5556a..0af00024 100644 --- a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigContext.java +++ b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigContext.java @@ -27,5 +27,10 @@ import org.springframework.core.env.Environment; * @author wind57 */ public record KubernetesClientConfigContext(CoreV1Api client, NormalizedSource normalizedSource, String namespace, - Environment environment) { + Environment environment, boolean includeDefaultProfileData) { + + public KubernetesClientConfigContext(CoreV1Api client, NormalizedSource normalizedSource, String namespace, + Environment environment) { + this(client, normalizedSource, namespace, environment, true); + } } diff --git a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapsCache.java b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapsCache.java index 488b09cf..b373a5f0 100644 --- a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapsCache.java +++ b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapsCache.java @@ -35,7 +35,7 @@ import org.springframework.core.log.LogAccessor; * * @author wind57 */ -final class KubernetesClientConfigMapsCache implements ConfigMapCache { +public final class KubernetesClientConfigMapsCache implements ConfigMapCache { private static final LogAccessor LOG = new LogAccessor(LogFactory.getLog(KubernetesClientConfigMapsCache.class)); 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 cfbeb100..ef4e3eda 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 @@ -108,12 +108,23 @@ public final class KubernetesClientConfigUtils { * */ static MultipleSourcesContainer secretsDataByName(CoreV1Api coreV1Api, String namespace, - LinkedHashSet sourceNames, Environment environment) { + LinkedHashSet sourceNames, Environment environment, boolean includeDefaultProfileData) { List strippedSecrets = strippedSecrets(coreV1Api, namespace); if (strippedSecrets.isEmpty()) { return MultipleSourcesContainer.empty(); } - return ConfigUtils.processNamedData(strippedSecrets, environment, sourceNames, namespace, DECODE); + return ConfigUtils.processNamedData(strippedSecrets, environment, sourceNames, namespace, DECODE, + includeDefaultProfileData); + } + + static MultipleSourcesContainer secretsDataByName(CoreV1Api coreV1Api, String namespace, + LinkedHashSet sourceNames, Environment environment) { + return secretsDataByName(coreV1Api, namespace, sourceNames, environment, true); + } + + static MultipleSourcesContainer configMapsDataByName(CoreV1Api coreV1Api, String namespace, + LinkedHashSet sourceNames, Environment environment) { + return configMapsDataByName(coreV1Api, namespace, sourceNames, environment, true); } /** @@ -125,12 +136,13 @@ public final class KubernetesClientConfigUtils { * */ static MultipleSourcesContainer configMapsDataByName(CoreV1Api coreV1Api, String namespace, - LinkedHashSet sourceNames, Environment environment) { + LinkedHashSet sourceNames, Environment environment, boolean includeDefaultProfileData) { List strippedConfigMaps = strippedConfigMaps(coreV1Api, namespace); if (strippedConfigMaps.isEmpty()) { return MultipleSourcesContainer.empty(); } - return ConfigUtils.processNamedData(strippedConfigMaps, environment, sourceNames, namespace, DECODE); + return ConfigUtils.processNamedData(strippedConfigMaps, environment, sourceNames, namespace, DECODE, + includeDefaultProfileData); } private static List strippedConfigMaps(CoreV1Api coreV1Api, String namespace) { 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 c19ee3fb..56645a4c 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 @@ -19,6 +19,7 @@ package org.springframework.cloud.kubernetes.client.config; import java.util.LinkedHashSet; import java.util.function.Supplier; +import org.springframework.cloud.kubernetes.commons.config.ConfigUtils; import org.springframework.cloud.kubernetes.commons.config.MultipleSourcesContainer; import org.springframework.cloud.kubernetes.commons.config.NamedConfigMapNormalizedSource; import org.springframework.cloud.kubernetes.commons.config.NamedSourceData; @@ -42,10 +43,19 @@ final class NamedConfigMapContextToSourceDataProvider implements Supplier sourceNames) { return KubernetesClientConfigUtils.configMapsDataByName(context.client(), context.namespace(), - sourceNames, context.environment()); + sourceNames, context.environment(), context.includeDefaultProfileData()); } }.compute(source.name().orElseThrow(), source.prefix(), source.target(), source.profileSpecificSources(), source.failFast(), context.namespace(), context.environment().getActiveProfiles()); 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 82eee371..bfde1400 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 @@ -19,6 +19,7 @@ package org.springframework.cloud.kubernetes.client.config; import java.util.LinkedHashSet; import java.util.function.Supplier; +import org.springframework.cloud.kubernetes.commons.config.ConfigUtils; import org.springframework.cloud.kubernetes.commons.config.MultipleSourcesContainer; import org.springframework.cloud.kubernetes.commons.config.NamedSecretNormalizedSource; import org.springframework.cloud.kubernetes.commons.config.NamedSourceData; @@ -41,10 +42,19 @@ final class NamedSecretContextToSourceDataProvider implements Supplier sourceNames) { return KubernetesClientConfigUtils.secretsDataByName(context.client(), context.namespace(), - sourceNames, context.environment()); + sourceNames, context.environment(), context.includeDefaultProfileData()); } }.compute(source.name().orElseThrow(), source.prefix(), source.target(), source.profileSpecificSources(), source.failFast(), context.namespace(), context.environment().getActiveProfiles()); diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/NamedConfigMapContextToSourceDataProviderTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/NamedConfigMapContextToSourceDataProviderTests.java index 81d065e6..63bcfa99 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/NamedConfigMapContextToSourceDataProviderTests.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/NamedConfigMapContextToSourceDataProviderTests.java @@ -164,17 +164,17 @@ class NamedConfigMapContextToSourceDataProviderTests { stubCall(configMapList); CoreV1Api api = new CoreV1Api(); - NormalizedSource source = new NamedConfigMapNormalizedSource(RED_CONFIG_MAP_NAME, NAMESPACE, true, true); + NormalizedSource source = new NamedConfigMapNormalizedSource(RED_CONFIG_MAP_NAME, NAMESPACE, true, + ConfigUtils.Prefix.DEFAULT, true, true); MockEnvironment environment = new MockEnvironment(); environment.setActiveProfiles("with-profile"); - KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, environment); + KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, environment, false); KubernetesClientContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); - Assertions.assertEquals(sourceData.sourceName(), "configmap.red.red-with-profile.default"); - Assertions.assertEquals(sourceData.sourceData().size(), 2); - Assertions.assertEquals(sourceData.sourceData().get("color"), "really-red"); + Assertions.assertEquals(sourceData.sourceName(), "configmap.red.red-with-profile.default.with-profile"); + Assertions.assertEquals(sourceData.sourceData().size(), 1); Assertions.assertEquals(sourceData.sourceData().get("taste"), "mango"); } diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/NamedSecretContextToSourceDataProviderTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/NamedSecretContextToSourceDataProviderTests.java index 9cb158b6..f5f92f7b 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/NamedSecretContextToSourceDataProviderTests.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/NamedSecretContextToSourceDataProviderTests.java @@ -217,17 +217,17 @@ class NamedSecretContextToSourceDataProviderTests { stubCall(secretList); CoreV1Api api = new CoreV1Api(); - NormalizedSource source = new NamedSecretNormalizedSource("red", NAMESPACE, false, true); + NormalizedSource source = new NamedSecretNormalizedSource("red", NAMESPACE, false, ConfigUtils.Prefix.DEFAULT, + true, true); MockEnvironment environment = new MockEnvironment(); environment.addActiveProfile("with-profile"); - KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, environment); + KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, environment, false); KubernetesClientContextToSourceData data = new NamedSecretContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); - Assertions.assertEquals(sourceData.sourceName(), "secret.red.red-with-profile.default"); - Assertions.assertEquals(sourceData.sourceData().size(), 2); - Assertions.assertEquals(sourceData.sourceData().get("color"), "really-red"); + Assertions.assertEquals(sourceData.sourceName(), "secret.red.red-with-profile.default.with-profile"); + Assertions.assertEquals(sourceData.sourceData().size(), 1); Assertions.assertEquals(sourceData.sourceData().get("taste"), "mango"); } 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 60197c39..00714b9a 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 @@ -17,6 +17,7 @@ package org.springframework.cloud.kubernetes.commons.config; import java.util.ArrayList; +import java.util.Arrays; import java.util.Base64; import java.util.HashMap; import java.util.LinkedHashSet; @@ -161,13 +162,27 @@ public final class ConfigUtils { return target + PROPERTY_SOURCE_NAME_SEPARATOR + applicationName + PROPERTY_SOURCE_NAME_SEPARATOR + namespace; } + public static String sourceName(String target, String applicationName, String namespace, String[] profiles) { + String name = sourceName(target, applicationName, namespace); + if (profiles != null && profiles.length > 0) { + name = name + PROPERTY_SOURCE_NAME_SEPARATOR + StringUtils.arrayToDelimitedString(profiles, "-"); + } + return name; + } + + public static MultipleSourcesContainer processNamedData(List strippedSources, + Environment environment, LinkedHashSet sourceNames, String namespace, boolean decode) { + return processNamedData(strippedSources, environment, sourceNames, namespace, decode, true); + } + /** * transforms raw data from one or multiple sources into an entry of source names and * flattened data that they all hold (potentially overriding entries without any * defined order). */ public static MultipleSourcesContainer processNamedData(List strippedSources, - Environment environment, LinkedHashSet sourceNames, String namespace, boolean decode) { + Environment environment, LinkedHashSet sourceNames, String namespace, boolean decode, + boolean includeDefaultProfileData) { Map hashByName = strippedSources.stream() .collect(Collectors.toMap(StrippedSourceContainer::name, Function.identity())); @@ -189,14 +204,35 @@ public final class ConfigUtils { if (decode) { rawData = decodeData(rawData); } - data.putAll(SourceDataEntriesProcessor.processAllEntries(rawData == null ? Map.of() : rawData, - environment)); + + // In some cases we want to include properties from the default profile along with any active profiles + // In these cases includeDefaultProfileData will be true + // If includeDefaultProfileData is false then we want to make sure that we only return properties from any active profiles + + //Check the source to see if it contains any active profiles + boolean containsActiveProfile = environment.getActiveProfiles().length == 0 + || Arrays.stream(environment.getActiveProfiles()) + .anyMatch(p -> source.contains("-" + p) || "default".equals(p)); + if (includeDefaultProfileData || containsActiveProfile + || containsDataWithProfile(rawData, environment.getActiveProfiles())) { + data.putAll(SourceDataEntriesProcessor.processAllEntries(rawData == null ? Map.of() : rawData, + environment, includeDefaultProfileData)); + } } }); return new MultipleSourcesContainer(foundSourceNames, data); } + /* + * In the case there the data contains yaml or properties files we need to check their names to see if they + * contain any active profiles. + */ + private static boolean containsDataWithProfile(Map rawData, String[] activeProfiles) { + return rawData.keySet().stream().anyMatch( + key -> Arrays.stream(activeProfiles).anyMatch(p -> key.contains("-" + p) || "default".equals(p))); + } + /** * transforms raw data from one or multiple sources into an entry of source names and * flattened data that they all hold (potentially overriding entries without any diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NamedConfigMapNormalizedSource.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NamedConfigMapNormalizedSource.java index 3d6b6085..6790cca8 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NamedConfigMapNormalizedSource.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NamedConfigMapNormalizedSource.java @@ -29,18 +29,25 @@ public final class NamedConfigMapNormalizedSource extends NormalizedSource { private final boolean includeProfileSpecificSources; + private final boolean appendProfileToName; + public NamedConfigMapNormalizedSource(String name, String namespace, boolean failFast, ConfigUtils.Prefix prefix, boolean includeProfileSpecificSources) { - super(name, namespace, failFast); - this.prefix = Objects.requireNonNull(prefix); - this.includeProfileSpecificSources = includeProfileSpecificSources; + this(name, namespace, failFast, prefix, includeProfileSpecificSources, false); } public NamedConfigMapNormalizedSource(String name, String namespace, boolean failFast, boolean includeProfileSpecificSources) { + this(name, namespace, failFast, ConfigUtils.Prefix.DEFAULT, includeProfileSpecificSources); + } + + public NamedConfigMapNormalizedSource(String name, String namespace, boolean failFast, ConfigUtils.Prefix prefix, + boolean includeProfileSpecificSources, boolean appendProfileToName) { super(name, namespace, failFast); - this.prefix = ConfigUtils.Prefix.DEFAULT; + this.prefix = Objects.requireNonNull(prefix); this.includeProfileSpecificSources = includeProfileSpecificSources; + this.appendProfileToName = appendProfileToName; + } public ConfigUtils.Prefix prefix() { @@ -51,6 +58,14 @@ public final class NamedConfigMapNormalizedSource extends NormalizedSource { return includeProfileSpecificSources; } + /** + * append or not the active profiles to the name of the generated source. + * At the moment this is true only for config server generated sources. + */ + public boolean appendProfileToName() { + return appendProfileToName; + } + @Override public NormalizedSourceType type() { return NormalizedSourceType.NAMED_CONFIG_MAP; diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NamedSecretNormalizedSource.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NamedSecretNormalizedSource.java index 4366ed1b..7a51a4d4 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NamedSecretNormalizedSource.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NamedSecretNormalizedSource.java @@ -29,24 +29,38 @@ public final class NamedSecretNormalizedSource extends NormalizedSource { private final boolean includeProfileSpecificSources; + private final boolean appendProfileToName; + public NamedSecretNormalizedSource(String name, String namespace, boolean failFast, ConfigUtils.Prefix prefix, - boolean includeProfileSpecificSources) { + boolean includeProfileSpecificSources, boolean appendProfileToName) { super(name, namespace, failFast); this.prefix = Objects.requireNonNull(prefix); this.includeProfileSpecificSources = includeProfileSpecificSources; + this.appendProfileToName = appendProfileToName; } public NamedSecretNormalizedSource(String name, String namespace, boolean failFast, boolean includeProfileSpecificSources) { - super(name, namespace, failFast); - this.prefix = ConfigUtils.Prefix.DEFAULT; - this.includeProfileSpecificSources = includeProfileSpecificSources; + this(name, namespace, failFast, ConfigUtils.Prefix.DEFAULT, includeProfileSpecificSources, false); + } + + public NamedSecretNormalizedSource(String name, String namespace, boolean failFast, ConfigUtils.Prefix prefix, + boolean includeProfileSpecificSources) { + this(name, namespace, failFast, prefix, includeProfileSpecificSources, false); } public boolean profileSpecificSources() { return includeProfileSpecificSources; } + /** + * append or not the active profiles to the name of the generated source. + * At the moment this is true only for config server generated sources. + */ + public boolean appendProfileToName() { + return appendProfileToName; + } + public ConfigUtils.Prefix prefix() { return prefix; } 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 fe7b12bf..f9846465 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 @@ -67,7 +67,11 @@ public abstract class NamedSourceData { } String names = data.names().stream().sorted().collect(Collectors.joining(PROPERTY_SOURCE_NAME_SEPARATOR)); - return new SourceData(ConfigUtils.sourceName(target, names, namespace), data.data()); + return new SourceData(generateSourceName(target, names, namespace, activeProfiles), data.data()); + } + + protected String generateSourceName(String target, String sourceName, String namespace, String[] activeProfiles) { + return ConfigUtils.sourceName(target, sourceName, namespace); } /** diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/SourceDataEntriesProcessor.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/SourceDataEntriesProcessor.java index 0e59fe5d..f1dcd14d 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/SourceDataEntriesProcessor.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/SourceDataEntriesProcessor.java @@ -24,7 +24,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Stream; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -53,6 +52,11 @@ public class SourceDataEntriesProcessor extends MapPropertySource { } public static Map processAllEntries(Map input, Environment environment) { + return processAllEntries(input, environment, true); + } + + public static Map processAllEntries(Map input, Environment environment, + boolean includeDefaultProfileData) { Set> entrySet = input.entrySet(); if (entrySet.size() == 1) { @@ -71,7 +75,11 @@ public class SourceDataEntriesProcessor extends MapPropertySource { } } - return defaultProcessAllEntries(input, environment); + return defaultProcessAllEntries(input, environment, includeDefaultProfileData); + } + + static List> sorted(Map input, Environment environment) { + return sorted(input, environment, true); } /** @@ -84,7 +92,8 @@ public class SourceDataEntriesProcessor extends MapPropertySource { * 3. then plain properties * */ - static List> sorted(Map input, Environment environment) { + static List> sorted(Map input, Environment environment, + boolean includeDefaultProfileData) { record WeightedEntry(Map.Entry entry, int weight) { @@ -95,37 +104,57 @@ public class SourceDataEntriesProcessor extends MapPropertySource { String applicationName = ConfigUtils.getApplicationName(environment, "", ""); String[] activeProfiles = environment.getActiveProfiles(); + // In some cases we want to include the properties from the default profile along with any properties from any active profiles + // In this case includeDefaultProfileData will be true or the active profile will be default + // In the case where includeDefaultProfileData is false we only want to include the properties from the active profiles + boolean includeDataEntry = includeDefaultProfileData || Arrays.asList(environment.getActiveProfiles()).contains("default"); + // the order here is important, first has to come "application.yaml" and then // "application-dev.yaml" - List orderedFileNames = Stream.concat(Stream.of(applicationName), - Arrays.stream(activeProfiles).map(profile -> applicationName + "-" + profile)).toList(); + List orderedFileNames = new ArrayList<>(); + if (includeDataEntry) { + orderedFileNames.add(applicationName); + } + orderedFileNames.addAll(Arrays.stream(activeProfiles).map(profile -> applicationName + "-" + profile).toList()); int current = orderedFileNames.size() - 1; List weightedEntries = new ArrayList<>(); - for (Map.Entry entry : input.entrySet()) { - String key = entry.getKey(); - if (key.endsWith(".yml") || key.endsWith(".yaml") || key.endsWith(".properties")) { - String withoutExtension = key.split("\\.", 2)[0]; - int index = orderedFileNames.indexOf(withoutExtension); - if (index >= 0) { - weightedEntries.add(new WeightedEntry(entry, index)); + + if (input.entrySet().stream().noneMatch(entry -> entry.getKey().endsWith(".yml") + || entry.getKey().endsWith(".yaml") || entry.getKey().endsWith(".properties"))) { + for (Map.Entry entry : input.entrySet()) { + weightedEntries.add(new WeightedEntry(entry, ++current)); + } + } + else { + for (Map.Entry entry : input.entrySet()) { + String key = entry.getKey(); + if (key.endsWith(".yml") || key.endsWith(".yaml") || key.endsWith(".properties")) { + String withoutExtension = key.split("\\.", 2)[0]; + int index = orderedFileNames.indexOf(withoutExtension); + if (index >= 0) { + weightedEntries.add(new WeightedEntry(entry, index)); + } + else { + LOG.warn("entry : " + key + " will be skipped"); + } } else { - LOG.warn("entry : " + key + " will be skipped"); + if (includeDataEntry) { + weightedEntries.add(new WeightedEntry(entry, ++current)); + } } } - else { - weightedEntries.add(new WeightedEntry(entry, ++current)); - } } return weightedEntries.stream().sorted(Comparator.comparing(WeightedEntry::weight)).map(WeightedEntry::entry) .toList(); } - private static Map defaultProcessAllEntries(Map input, Environment environment) { + private static Map defaultProcessAllEntries(Map input, Environment environment, + boolean includeDefaultProfile) { - List> sortedEntries = sorted(input, environment); + List> sortedEntries = sorted(input, environment, includeDefaultProfile); Map result = new HashMap<>(); for (Map.Entry entry : sortedEntries) { result.putAll(extractProperties(entry.getKey(), entry.getValue(), environment)); diff --git a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/SourceDataEntriesProcessorSortedTests.java b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/SourceDataEntriesProcessorSortedTests.java index 34106fc9..769f079d 100644 --- a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/SourceDataEntriesProcessorSortedTests.java +++ b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/SourceDataEntriesProcessorSortedTests.java @@ -135,6 +135,24 @@ class SourceDataEntriesProcessorSortedTests { Assertions.assertEquals(result.get(2).getValue(), "other_value"); } + @Test + void testSimplePropertyAndTwoApplicationsDoNotIncludeDefaultProfile() { + + Map k8sSource = new LinkedHashMap<>(); + k8sSource.put("simple", "other_value"); + k8sSource.put("application.properties", "key=value"); + k8sSource.put("application-dev.properties", "key-dev=value-dev"); + + MockEnvironment mockEnvironment = new MockEnvironment(); + mockEnvironment.setActiveProfiles("dev"); + + List> result = SourceDataEntriesProcessor.sorted(k8sSource, mockEnvironment, false); + Assertions.assertEquals(1, result.size()); + + Assertions.assertEquals(result.get(0).getKey(), "application-dev.properties"); + Assertions.assertEquals(result.get(0).getValue(), "key-dev=value-dev"); + } + @Test void testComplex() { diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesConfigServerAutoConfiguration.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesConfigServerAutoConfiguration.java index 66e48948..1028151d 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesConfigServerAutoConfiguration.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesConfigServerAutoConfiguration.java @@ -36,6 +36,7 @@ import org.springframework.cloud.kubernetes.client.config.KubernetesClientSecret import org.springframework.cloud.kubernetes.commons.ConditionalOnKubernetesConfigEnabled; import org.springframework.cloud.kubernetes.commons.ConditionalOnKubernetesSecretsEnabled; import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider; +import org.springframework.cloud.kubernetes.commons.config.ConfigUtils; import org.springframework.cloud.kubernetes.commons.config.NamedConfigMapNormalizedSource; import org.springframework.cloud.kubernetes.commons.config.NamedSecretNormalizedSource; import org.springframework.cloud.kubernetes.commons.config.NormalizedSource; @@ -77,9 +78,9 @@ public class KubernetesConfigServerAutoConfiguration { namespaces.forEach(space -> { NamedConfigMapNormalizedSource source = new NamedConfigMapNormalizedSource(applicationName, space, - false, true); + false, ConfigUtils.Prefix.DEFAULT, true, true); KubernetesClientConfigContext context = new KubernetesClientConfigContext(coreApi, source, space, - springEnv); + springEnv, false); propertySources.add(new KubernetesClientConfigMapPropertySource(context)); }); @@ -96,9 +97,10 @@ public class KubernetesConfigServerAutoConfiguration { List propertySources = new ArrayList<>(); namespaces.forEach(space -> { - NormalizedSource source = new NamedSecretNormalizedSource(applicationName, space, false, false); + NormalizedSource source = new NamedSecretNormalizedSource(applicationName, space, false, + ConfigUtils.Prefix.DEFAULT, true, true); KubernetesClientConfigContext context = new KubernetesClientConfigContext(coreApi, source, space, - springEnv); + springEnv, false); propertySources.add(new KubernetesClientSecretsPropertySource(context)); }); diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesConfigServerEnvironment.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesConfigServerEnvironment.java new file mode 100644 index 00000000..61eba5cc --- /dev/null +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesConfigServerEnvironment.java @@ -0,0 +1,31 @@ +/* + * Copyright 2013-2024 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.configserver; + +import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.StandardEnvironment; + +/** + * @author Ryan Baxter + */ +public class KubernetesConfigServerEnvironment extends StandardEnvironment { + + KubernetesConfigServerEnvironment(MutablePropertySources mutablePropertySources) { + super(mutablePropertySources); + } + +} diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesEnvironmentRepository.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesEnvironmentRepository.java index 9f5296ac..3c25e7f9 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesEnvironmentRepository.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesEnvironmentRepository.java @@ -16,7 +16,11 @@ package org.springframework.cloud.kubernetes.configserver; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import io.kubernetes.client.openapi.apis.CoreV1Api; import org.apache.commons.logging.Log; @@ -26,6 +30,7 @@ import org.springframework.cloud.config.environment.Environment; import org.springframework.cloud.config.environment.PropertySource; import org.springframework.cloud.config.server.environment.EnvironmentRepository; import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.StandardEnvironment; import org.springframework.util.StringUtils; @@ -56,26 +61,51 @@ public class KubernetesEnvironmentRepository implements EnvironmentRepository { @Override public Environment findOne(String application, String profile, String label, boolean includeOrigin) { - String[] profiles = StringUtils.commaDelimitedListToStringArray(profile); + if (!StringUtils.hasText(profile)) { + profile = "default"; + } + List profiles = new java.util.ArrayList<>( + Arrays.stream(StringUtils.commaDelimitedListToStringArray(profile)).toList()); + + Collections.reverse(profiles); + if (!profiles.contains("default")) { + profiles.add("default"); + } + Environment environment = new Environment(application, profiles.toArray(profiles.toArray(new String[0])), label, + null, null); LOG.info("Profiles: " + profile); LOG.info("Application: " + application); LOG.info("Label: " + label); - Environment environment = new Environment(application, profiles, label, null, null); - try { - StandardEnvironment springEnv = new StandardEnvironment(); - springEnv.setActiveProfiles(profiles); - if (!"application".equalsIgnoreCase(application)) { - addApplicationConfiguration(environment, springEnv, application); + for (String activeProfile : profiles) { + try { + // This is needed so that when we get the application name in + // SourceDataProcessor.sorted that it actually + // exists in the Environment + StandardEnvironment springEnv = new KubernetesConfigServerEnvironment( + createPropertySources(application)); + springEnv.setActiveProfiles(activeProfile); + if (!"application".equalsIgnoreCase(application)) { + addApplicationConfiguration(environment, springEnv, application); + } + } + catch (Exception e) { + LOG.warn(e); } - addApplicationConfiguration(environment, springEnv, "application"); - return environment; - } - catch (Exception e) { - LOG.warn(e); } + StandardEnvironment springEnv = new KubernetesConfigServerEnvironment(createPropertySources("application")); + addApplicationConfiguration(environment, springEnv, "application"); return environment; } + private MutablePropertySources createPropertySources(String application) { + Map applicationProperties = new HashMap<>(); + applicationProperties.put("spring.application.name", application); + MapPropertySource propertySource = new MapPropertySource("kubernetes-config-server", applicationProperties); + MutablePropertySources mutablePropertySources = new MutablePropertySources(); + mutablePropertySources.addFirst(propertySource); + return mutablePropertySources; + } + private void addApplicationConfiguration(Environment environment, StandardEnvironment springEnv, String applicationName) { kubernetesPropertySourceSuppliers.stream().forEach(supplier -> { diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/KubernetesEnvironmentRepositoryTests.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/KubernetesEnvironmentRepositoryTests.java index fcb6e586..4b180057 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/KubernetesEnvironmentRepositoryTests.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/KubernetesEnvironmentRepositoryTests.java @@ -27,6 +27,7 @@ import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder; import io.kubernetes.client.openapi.models.V1SecretBuilder; import io.kubernetes.client.openapi.models.V1SecretList; import io.kubernetes.client.openapi.models.V1SecretListBuilder; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -34,7 +35,9 @@ import org.junit.jupiter.api.Test; import org.springframework.cloud.config.environment.Environment; import org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigContext; import org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigMapPropertySource; +import org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigMapsCache; import org.springframework.cloud.kubernetes.client.config.KubernetesClientSecretsPropertySource; +import org.springframework.cloud.kubernetes.commons.config.ConfigUtils; import org.springframework.cloud.kubernetes.commons.config.NamedConfigMapNormalizedSource; import org.springframework.cloud.kubernetes.commons.config.NamedSecretNormalizedSource; import org.springframework.cloud.kubernetes.commons.config.NormalizedSource; @@ -56,6 +59,19 @@ class KubernetesEnvironmentRepositoryTests { private static final String DEFAULT_NAMESPACE = "default"; + private static final V1ConfigMapList CONFIGMAP_ONE_LIST = new V1ConfigMapList() + .addItemsItem(new V1ConfigMapBuilder() + .withMetadata( + new V1ObjectMetaBuilder().withName("storessingle").withNamespace(DEFAULT_NAMESPACE).build()) + .addToData("storessingle.yaml", VALUE) + .addToData("storessingle-dev.yaml", + "dummy:\n property:\n string2: \"dev\"\n int2: 1\n bool2: false\n") + .addToData("storessingle-qa.yaml", + "dummy:\n property:\n string2: \"qa\"\n int2: 2\n bool2: true\n") + .addToData("storessingle-prod.yaml", + "dummy:\n property:\n string2: \"prod\"\n int2: 3\n bool2: true\n") + .build()); + private static final V1ConfigMapList CONFIGMAP_DEFAULT_LIST = new V1ConfigMapList() .addItemsItem(new V1ConfigMapBuilder() .withMetadata( @@ -105,13 +121,14 @@ class KubernetesEnvironmentRepositoryTests { true); KubernetesClientConfigContext defaultContext = new KubernetesClientConfigContext(coreApi, defaultSource, "default", springEnv); - - NormalizedSource devSource = new NamedConfigMapNormalizedSource(applicationName, "dev", false, true); - KubernetesClientConfigContext devContext = new KubernetesClientConfigContext(coreApi, devSource, "dev", - springEnv); - propertySources.add(new KubernetesClientConfigMapPropertySource(defaultContext)); - propertySources.add(new KubernetesClientConfigMapPropertySource(devContext)); + + if ("stores".equals(applicationName) && "dev".equals(namespace)) { + NormalizedSource devSource = new NamedConfigMapNormalizedSource(applicationName, "dev", false, true); + KubernetesClientConfigContext devContext = new KubernetesClientConfigContext(coreApi, devSource, "dev", + springEnv); + propertySources.add(new KubernetesClientConfigMapPropertySource(devContext)); + } return propertySources; }); KUBERNETES_PROPERTY_SOURCE_SUPPLIER.add((coreApi, applicationName, namespace, springEnv) -> { @@ -126,6 +143,11 @@ class KubernetesEnvironmentRepositoryTests { }); } + @AfterEach + public void after() { + new KubernetesClientConfigMapsCache().discardAll(); + } + @Test public void testApplicationCase() throws ApiException { CoreV1Api coreApi = mock(CoreV1Api.class); @@ -168,12 +190,11 @@ class KubernetesEnvironmentRepositoryTests { KubernetesEnvironmentRepository environmentRepository = new KubernetesEnvironmentRepository(coreApi, KUBERNETES_PROPERTY_SOURCE_SUPPLIER, "default"); Environment environment = environmentRepository.findOne("stores", "", ""); - assertThat(environment.getPropertySources().size()).isEqualTo(5); + assertThat(environment.getPropertySources().size()).isEqualTo(4); environment.getPropertySources().forEach(propertySource -> { assertThat(propertySource.getName().equals("configmap.application.default") || propertySource.getName().equals("secret.application.default") || propertySource.getName().equals("configmap.stores.default") - || propertySource.getName().equals("configmap.stores.dev") || propertySource.getName().equals("secret.stores.default")).isTrue(); if (propertySource.getName().equals("configmap.application.default")) { assertThat(propertySource.getSource().size()).isEqualTo(3); @@ -192,12 +213,6 @@ class KubernetesEnvironmentRepositoryTests { assertThat(propertySource.getSource().get("dummy.property.bool2")).isEqualTo(true); assertThat(propertySource.getSource().get("dummy.property.string2")).isEqualTo("a"); } - if (propertySource.getName().equals("configmap.stores.dev")) { - assertThat(propertySource.getSource().size()).isEqualTo(3); - assertThat(propertySource.getSource().get("dummy.property.int2")).isEqualTo(1); - assertThat(propertySource.getSource().get("dummy.property.bool2")).isEqualTo(true); - assertThat(propertySource.getSource().get("dummy.property.string2")).isEqualTo("dev"); - } if (propertySource.getName().equals("secret.stores.default")) { assertThat(propertySource.getSource().size()).isEqualTo(2); assertThat(propertySource.getSource().get("username")).isEqualTo("stores"); @@ -218,13 +233,14 @@ class KubernetesEnvironmentRepositoryTests { KubernetesEnvironmentRepository environmentRepository = new KubernetesEnvironmentRepository(coreApi, KUBERNETES_PROPERTY_SOURCE_SUPPLIER, "default"); Environment environment = environmentRepository.findOne("stores", "dev", ""); - assertThat(environment.getPropertySources().size()).isEqualTo(5); + assertThat(environment.getPropertySources().size()).isEqualTo(6); environment.getPropertySources().forEach(propertySource -> { assertThat(propertySource.getName().equals("configmap.application.default") || propertySource.getName().equals("secret.application.default") || propertySource.getName().equals("configmap.stores.stores-dev.default") - || propertySource.getName().equals("configmap.stores.dev") - || propertySource.getName().equals("secret.stores.stores-dev.default")).isTrue(); + || propertySource.getName().equals("secret.stores.default") + || propertySource.getName().equals("secret.stores.stores-dev.default") + || propertySource.getName().equals("configmap.stores.default")).isTrue(); if (propertySource.getName().equals("configmap.application.default")) { assertThat(propertySource.getSource().size()).isEqualTo(3); assertThat(propertySource.getSource().get("dummy.property.int2")).isEqualTo(1); @@ -236,24 +252,29 @@ class KubernetesEnvironmentRepositoryTests { assertThat(propertySource.getSource().get("username")).isEqualTo("user"); assertThat(propertySource.getSource().get("password")).isEqualTo("p455w0rd"); } - else if (propertySource.getName().equals("configmap.stores.stores-dev.default")) { - assertThat(propertySource.getSource().size()).isEqualTo(4); - assertThat(propertySource.getSource().get("dummy.property.int2")).isEqualTo(2); - assertThat(propertySource.getSource().get("dummy.property.bool2")).isEqualTo(false); - assertThat(propertySource.getSource().get("dummy.property.string2")).isEqualTo("b"); - assertThat(propertySource.getSource().get("dummy.property.string1")).isEqualTo("a"); - } - else if (propertySource.getName().equals("configmap.stores.dev")) { - assertThat(propertySource.getSource().size()).isEqualTo(3); - assertThat(propertySource.getSource().get("dummy.property.int2")).isEqualTo(1); - assertThat(propertySource.getSource().get("dummy.property.bool2")).isEqualTo(true); - assertThat(propertySource.getSource().get("dummy.property.string2")).isEqualTo("dev"); - } else if (propertySource.getName().equals("secret.stores.stores-dev.default")) { assertThat(propertySource.getSource().size()).isEqualTo(2); assertThat(propertySource.getSource().get("username")).isEqualTo("stores-dev"); assertThat(propertySource.getSource().get("password")).isEqualTo("password-from-stores-dev"); } + else if (propertySource.getName().equals("configmap.stores.stores-dev.default")) { + assertThat(propertySource.getSource().size()).isEqualTo(4); + assertThat(propertySource.getSource().get("dummy.property.int2")).isEqualTo(2); + assertThat(propertySource.getSource().get("dummy.property.bool2")).isEqualTo(false); + assertThat(propertySource.getSource().get("dummy.property.string1")).isEqualTo("a"); + assertThat(propertySource.getSource().get("dummy.property.string2")).isEqualTo("b"); + } + else if (propertySource.getName().equals("configmap.stores.default")) { + assertThat(propertySource.getSource().size()).isEqualTo(3); + assertThat(propertySource.getSource().get("dummy.property.int2")).isEqualTo(1); + assertThat(propertySource.getSource().get("dummy.property.bool2")).isEqualTo(true); + assertThat(propertySource.getSource().get("dummy.property.string2")).isEqualTo("a"); + } + else if (propertySource.getName().equals("secret.stores.default")) { + assertThat(propertySource.getSource().size()).isEqualTo(2); + assertThat(propertySource.getSource().get("username")).isEqualTo("stores"); + assertThat(propertySource.getSource().get("password")).isEqualTo("password-from-stores"); + } else { Assertions.fail("no match in property source names"); } @@ -292,4 +313,52 @@ class KubernetesEnvironmentRepositoryTests { }); } + @Test + public void testSingleConfigMapMultipleSources() throws ApiException { + CoreV1Api coreApi = mock(CoreV1Api.class); + when(coreApi.listNamespacedConfigMap(eq("default"), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), + eq(null), eq(null), eq(null), eq(null), eq(null))).thenReturn(CONFIGMAP_ONE_LIST); + when(coreApi.listNamespacedSecret(eq("default"), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), + eq(null), eq(null), eq(null), eq(null), eq(null))).thenReturn(new V1SecretList()); + List suppliers = new ArrayList<>(); + suppliers.add((coreV1Api, name, namespace, environment) -> { + List propertySources = new ArrayList<>(); + NormalizedSource devSource = new NamedConfigMapNormalizedSource(name, namespace, false, + ConfigUtils.Prefix.DEFAULT, true, true); + KubernetesClientConfigContext devContext = new KubernetesClientConfigContext(coreApi, devSource, "default", + environment); + propertySources.add(new KubernetesClientConfigMapPropertySource(devContext)); + return propertySources; + }); + KubernetesEnvironmentRepository environmentRepository = new KubernetesEnvironmentRepository(coreApi, suppliers, + "default"); + Environment environment = environmentRepository.findOne("storessingle", "", ""); + assertThat(environment.getPropertySources().size()).isEqualTo(1); + assertThat(environment.getPropertySources().get(0).getName()) + .isEqualTo("configmap.storessingle.default.default"); + + environment = environmentRepository.findOne("storessingle", "dev", ""); + assertThat(environment.getPropertySources().size()).isEqualTo(2); + assertThat(environment.getPropertySources().get(0).getName()).isEqualTo("configmap.storessingle.default.dev"); + assertThat(environment.getPropertySources().get(1).getName()) + .isEqualTo("configmap.storessingle.default.default"); + + environment = environmentRepository.findOne("storessingle", "dev,prod", ""); + assertThat(environment.getPropertySources().size()).isEqualTo(3); + assertThat(environment.getPropertySources().get(0).getName()).isEqualTo("configmap.storessingle.default.prod"); + assertThat(environment.getPropertySources().get(0).getSource().get("dummy.property.int2")).isEqualTo(3); + assertThat(environment.getPropertySources().get(0).getSource().get("dummy.property.bool2")).isEqualTo(true); + assertThat(environment.getPropertySources().get(0).getSource().get("dummy.property.string2")).isEqualTo("prod"); + assertThat(environment.getPropertySources().get(1).getName()).isEqualTo("configmap.storessingle.default.dev"); + assertThat(environment.getPropertySources().get(1).getSource().get("dummy.property.int2")).isEqualTo(1); + assertThat(environment.getPropertySources().get(1).getSource().get("dummy.property.bool2")).isEqualTo(false); + assertThat(environment.getPropertySources().get(1).getSource().get("dummy.property.string2")).isEqualTo("dev"); + assertThat(environment.getPropertySources().get(2).getName()) + .isEqualTo("configmap.storessingle.default.default"); + assertThat(environment.getPropertySources().get(2).getSource().get("dummy.property.int2")).isEqualTo(1); + assertThat(environment.getPropertySources().get(2).getSource().get("dummy.property.bool2")).isEqualTo(true); + assertThat(environment.getPropertySources().get(2).getSource().get("dummy.property.string2")).isEqualTo("a"); + + } + } diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/KubernetesPropertySourceSupplierTests.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/KubernetesPropertySourceSupplierTests.java index 1b9188e4..8e7e44ce 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/KubernetesPropertySourceSupplierTests.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/KubernetesPropertySourceSupplierTests.java @@ -49,7 +49,7 @@ class KubernetesPropertySourceSupplierTests { private static final CoreV1Api coreApi = mock(CoreV1Api.class); private static final V1ConfigMapList CONFIGMAP_DEFAULT_LIST = new V1ConfigMapList() - .addItemsItem(buildConfigMap("gateway", "tdefault")); + .addItemsItem(buildConfigMap("gateway", "default")); private static final V1ConfigMapList CONFIGMAP_TEAM_A_LIST = new V1ConfigMapList() .addItemsItem(buildConfigMap("stores", "team-a")); diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/it/ConfigServerIntegrationTest.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/it/ConfigServerIntegrationTest.java index c88ecfa9..b103521d 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/it/ConfigServerIntegrationTest.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/it/ConfigServerIntegrationTest.java @@ -51,8 +51,17 @@ abstract class ConfigServerIntegrationTest { @BeforeEach void beforeEach() { - V1ConfigMapList TEST_CONFIGMAP = new V1ConfigMapList().addItemsItem(new V1ConfigMapBuilder().withMetadata( - new V1ObjectMetaBuilder().withName("test-cm").withNamespace("default").withResourceVersion("1").build()) + V1ConfigMapList TEST_CONFIGMAP = new V1ConfigMapList().addItemsItem(new V1ConfigMapBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("test-cm").withNamespace("default") + .withResourceVersion("1").build()) + .addToData("test-cm-dev.yaml", + "dummy:\n property:\n string2: \"dev\"\n int2: 1\n bool2: false\n") + .addToData("test-cm-qa.yaml", + "dummy:\n property:\n string2: \"qa\"\n int2: 2\n bool2: true\n") + .addToData("test-cm-prod.yaml", + "dummy:\n property:\n string2: \"prod\"\n int2: 3\n bool2: true\n") + .addToData("test-cm.yaml", + "dummy:\n property:\n string2: \"default\"\n int2: 4\n bool2: true\n") .addToData("app.name", "test").build()); V1SecretList TEST_SECRET = new V1SecretListBuilder() @@ -74,11 +83,34 @@ abstract class ConfigServerIntegrationTest { void enabled() { Environment env = testRestTemplate.getForObject("/test-cm/default", Environment.class); assertThat(env.getPropertySources().size()).isEqualTo(2); - assertThat(env.getPropertySources().get(0).getName().equals("configmap.test-cm.default")).isTrue(); + assertThat(env.getPropertySources().get(0).getName().equals("configmap.test-cm.default.default")).isTrue(); assertThat(env.getPropertySources().get(0).getSource().get("app.name")).isEqualTo("test"); - assertThat(env.getPropertySources().get(1).getName().equals("secret.test-cm.default")).isTrue(); + assertThat(env.getPropertySources().get(1).getName().equals("secret.test-cm.default.default")).isTrue(); assertThat(env.getPropertySources().get(1).getSource().get("password")).isEqualTo("p455w0rd"); assertThat(env.getPropertySources().get(1).getSource().get("username")).isEqualTo("user"); + + Environment devprod = testRestTemplate.getForObject("/test-cm/dev,prod", Environment.class); + assertThat(devprod.getPropertySources().size()).isEqualTo(4); + assertThat(devprod.getPropertySources().get(0).getName().equals("configmap.test-cm.default.prod")).isTrue(); + assertThat(devprod.getPropertySources().get(0).getSource().size()).isEqualTo(3); + assertThat(devprod.getPropertySources().get(0).getSource().get("dummy.property.int2")).isEqualTo(3); + assertThat(devprod.getPropertySources().get(0).getSource().get("dummy.property.bool2")).isEqualTo(true); + assertThat(devprod.getPropertySources().get(0).getSource().get("dummy.property.string2")).isEqualTo("prod"); + assertThat(devprod.getPropertySources().get(1).getName().equals("configmap.test-cm.default.dev")).isTrue(); + assertThat(devprod.getPropertySources().get(1).getSource().size()).isEqualTo(3); + assertThat(devprod.getPropertySources().get(1).getSource().get("dummy.property.int2")).isEqualTo(1); + assertThat(devprod.getPropertySources().get(1).getSource().get("dummy.property.bool2")).isEqualTo(false); + assertThat(devprod.getPropertySources().get(1).getSource().get("dummy.property.string2")).isEqualTo("dev"); + assertThat(devprod.getPropertySources().get(2).getName().equals("configmap.test-cm.default.default")).isTrue(); + assertThat(devprod.getPropertySources().get(2).getSource().size()).isEqualTo(4); + assertThat(devprod.getPropertySources().get(2).getSource().get("app.name")).isEqualTo("test"); + assertThat(devprod.getPropertySources().get(2).getSource().get("dummy.property.int2")).isEqualTo(4); + assertThat(devprod.getPropertySources().get(2).getSource().get("dummy.property.bool2")).isEqualTo(true); + assertThat(devprod.getPropertySources().get(2).getSource().get("dummy.property.string2")).isEqualTo("default"); + assertThat(devprod.getPropertySources().get(3).getName().equals("secret.test-cm.default.default")).isTrue(); + assertThat(devprod.getPropertySources().get(3).getSource().size()).isEqualTo(2); + assertThat(devprod.getPropertySources().get(3).getSource().get("password")).isEqualTo("p455w0rd"); + assertThat(devprod.getPropertySources().get(3).getSource().get("username")).isEqualTo("user"); } } 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 d1785c19..3ac61a7c 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 @@ -19,6 +19,7 @@ package org.springframework.cloud.kubernetes.fabric8.config; import java.util.LinkedHashSet; import java.util.function.Supplier; +import org.springframework.cloud.kubernetes.commons.config.ConfigUtils; import org.springframework.cloud.kubernetes.commons.config.MultipleSourcesContainer; import org.springframework.cloud.kubernetes.commons.config.NamedConfigMapNormalizedSource; import org.springframework.cloud.kubernetes.commons.config.NamedSourceData; @@ -49,6 +50,16 @@ final class NamedConfigMapContextToSourceDataProvider implements Supplier sourceNames) { return Fabric8ConfigUtils.configMapsDataByName(context.client(), context.namespace(), sourceNames, 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 f89303f0..5a235608 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 @@ -19,6 +19,7 @@ package org.springframework.cloud.kubernetes.fabric8.config; import java.util.LinkedHashSet; import java.util.function.Supplier; +import org.springframework.cloud.kubernetes.commons.config.ConfigUtils; import org.springframework.cloud.kubernetes.commons.config.MultipleSourcesContainer; import org.springframework.cloud.kubernetes.commons.config.NamedSecretNormalizedSource; import org.springframework.cloud.kubernetes.commons.config.NamedSourceData; @@ -40,6 +41,15 @@ final class NamedSecretContextToSourceDataProvider implements Supplier sourceNames) { return Fabric8ConfigUtils.secretsDataByName(context.client(), context.namespace(), sourceNames, diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/NamedConfigMapContextToSourceDataProviderTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/NamedConfigMapContextToSourceDataProviderTests.java index 61f4f838..8cee57c2 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/NamedConfigMapContextToSourceDataProviderTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/NamedConfigMapContextToSourceDataProviderTests.java @@ -148,14 +148,15 @@ class NamedConfigMapContextToSourceDataProviderTests { // add one more profile and specify that we want profile based config maps MockEnvironment env = new MockEnvironment(); env.setActiveProfiles("with-profile"); - NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("red", NAMESPACE, true, true); + NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("red", NAMESPACE, true, + ConfigUtils.Prefix.DEFAULT, true, true); Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, env); Fabric8ContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); - Assertions.assertEquals(sourceData.sourceName(), "configmap.red.red-with-profile.default"); + Assertions.assertEquals(sourceData.sourceName(), "configmap.red.red-with-profile.default.with-profile"); Assertions.assertEquals(sourceData.sourceData().size(), 2); Assertions.assertEquals(sourceData.sourceData().get("color"), "really-red"); Assertions.assertEquals(sourceData.sourceData().get("taste"), "mango"); diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/NamedSecretContextToSourceDataProviderTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/NamedSecretContextToSourceDataProviderTests.java index 62285456..f92b3383 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/NamedSecretContextToSourceDataProviderTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/NamedSecretContextToSourceDataProviderTests.java @@ -196,13 +196,14 @@ class NamedSecretContextToSourceDataProviderTests { // add one more profile and specify that we want profile based config maps MockEnvironment env = new MockEnvironment(); env.setActiveProfiles("with-profile"); - NormalizedSource normalizedSource = new NamedSecretNormalizedSource("red", NAMESPACE, true, true); + NormalizedSource normalizedSource = new NamedSecretNormalizedSource("red", NAMESPACE, true, + ConfigUtils.Prefix.DEFAULT, true, true); Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, env); Fabric8ContextToSourceData data = new NamedSecretContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); - Assertions.assertEquals(sourceData.sourceName(), "secret.red.red-with-profile.default"); + Assertions.assertEquals(sourceData.sourceName(), "secret.red.red-with-profile.default.with-profile"); Assertions.assertEquals(sourceData.sourceData().size(), 2); Assertions.assertEquals(sourceData.sourceData().get("color"), "really-red"); Assertions.assertEquals(sourceData.sourceData().get("taste"), "mango");