proper order fix (#1052)

* proper order fix

* trigger

* trigger

* trigger
This commit is contained in:
erabii
2022-08-18 21:23:45 +03:00
committed by GitHub
parent a609ac7ae7
commit 6fb7bcb171
28 changed files with 297 additions and 131 deletions

View File

@@ -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
* </pre>
*/
static MultipleSourcesContainer secretsDataByName(CoreV1Api client, String namespace, Set<String> sourceNames,
Environment environment) {
static MultipleSourcesContainer secretsDataByName(CoreV1Api client, String namespace,
LinkedHashSet<String> sourceNames, Environment environment) {
List<V1Secret> 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
* </pre>
*/
static MultipleSourcesContainer configMapsDataByName(CoreV1Api client, String namespace, Set<String> sourceNames,
Environment environment) {
static MultipleSourcesContainer configMapsDataByName(CoreV1Api client, String namespace,
LinkedHashSet<String> sourceNames, Environment environment) {
List<V1ConfigMap> configMaps = configMapsSearch(client, namespace);
if (ConfigUtils.noSources(configMaps, namespace)) {
return MultipleSourcesContainer.empty();

View File

@@ -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<Kubern
return new NamedSourceData() {
@Override
public MultipleSourcesContainer dataSupplier(Set<String> sourceNames) {
public MultipleSourcesContainer dataSupplier(LinkedHashSet<String> sourceNames) {
return KubernetesClientConfigUtils.configMapsDataByName(context.client(), context.namespace(),
sourceNames, context.environment());
}

View File

@@ -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<Kubernete
return new NamedSourceData() {
@Override
public MultipleSourcesContainer dataSupplier(Set<String> sourceNames) {
public MultipleSourcesContainer dataSupplier(LinkedHashSet<String> sourceNames) {
return KubernetesClientConfigUtils.secretsDataByName(context.client(), context.namespace(),
sourceNames, context.environment());
}

View File

@@ -61,15 +61,16 @@ abstract class LabeledConfigMapWithProfileTests {
/**
* <pre>
* 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.
* </pre>
*/
@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"));
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -73,15 +73,16 @@ abstract class LabeledSecretWithProfileTests {
/**
* <pre>
* 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.
* </pre>
*/
@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"));
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -64,16 +64,21 @@ public class LabeledConfigMapWithProfileConfigurationStub {
return apiClient;
}
/*
* <pre> - 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}" </pre>
/**
* <pre>
* - 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}"
* </pre>
*/
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))));

View File

@@ -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;
}
/*
* <pre> - 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}" </pre>
/**
* <pre>
* - 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}"
* </pre>
*/
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))));

View File

@@ -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();

View File

@@ -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())

View File

@@ -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<StrippedSourceContainer> strippedSources,
Environment environment, Set<String> sourceNames, String namespace, boolean decode) {
Environment environment, LinkedHashSet<String> sourceNames, String namespace, boolean decode) {
Set<String> foundSourceNames = new HashSet<>();
Map<String, StrippedSourceContainer> hashByName = strippedSources.stream()
.collect(Collectors.toMap(StrippedSourceContainer::name, Function.identity()));
LinkedHashSet<String> foundSourceNames = new LinkedHashSet<>();
Map<String, Object> 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<String, String> 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<String, String> 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<StrippedSourceContainer> sourcesByLabels = containers.stream().filter(one -> {
List<StrippedSourceContainer> byLabels = containers.stream().filter(one -> {
Map<String, String> sourceLabels = one.labels();
Map<String, String> 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<String> 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<StrippedSourceContainer> sourcesToTake = containers.stream()
.filter(one -> sourceNamesByLabelsWithProfile.contains(one.name()))
.collect(Collectors.toCollection(ArrayList::new));
sourcesToTake.addAll(sourcesByLabels);
List<StrippedSourceContainer> byProfile = containers.stream()
.filter(one -> sourceNamesByLabelsWithProfile.contains(one.name())).collect(Collectors.toList());
Set<String> sourceNames = new HashSet<>();
// this makes sure that we first have "app" and then "app-dev" in the list
List<StrippedSourceContainer> all = new ArrayList<>(byLabels.size() + byProfile.size());
all.addAll(byLabels);
all.addAll(byProfile);
LinkedHashSet<String> sourceNames = new LinkedHashSet<>();
Map<String, Object> 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);

View File

@@ -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<String> names, Map<String, Object> data) {
public final record MultipleSourcesContainer(LinkedHashSet<String> names, Map<String, Object> 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;

View File

@@ -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<String> sourceNames = new HashSet<>();
sourceNames.add(initialSourceName);
LinkedHashSet<String> 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<String> sourceNames);
public abstract MultipleSourcesContainer dataSupplier(LinkedHashSet<String> sourceNames);
}

View File

@@ -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 {
* </pre>
*/
static MultipleSourcesContainer secretsDataByName(KubernetesClient client, String namespace,
Set<String> sourceNames, Environment environment) {
LinkedHashSet<String> sourceNames, Environment environment) {
List<Secret> secrets = secretsSearch(client, namespace);
if (ConfigUtils.noSources(secrets, namespace)) {
return MultipleSourcesContainer.empty();
@@ -181,7 +182,7 @@ public final class Fabric8ConfigUtils {
* </pre>
*/
static MultipleSourcesContainer configMapsDataByName(KubernetesClient client, String namespace,
Set<String> sourceNames, Environment environment) {
LinkedHashSet<String> sourceNames, Environment environment) {
List<ConfigMap> configMaps = configMapsSearch(client, namespace);
if (ConfigUtils.noSources(configMaps, namespace)) {
return MultipleSourcesContainer.empty();

View File

@@ -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<Fabric
return new NamedSourceData() {
@Override
public MultipleSourcesContainer dataSupplier(Set<String> sourceNames) {
public MultipleSourcesContainer dataSupplier(LinkedHashSet<String> sourceNames) {
return Fabric8ConfigUtils.configMapsDataByName(context.client(), context.namespace(), sourceNames,
context.environment());
}

View File

@@ -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<Fabric8Co
return new NamedSourceData() {
@Override
public MultipleSourcesContainer dataSupplier(Set<String> sourceNames) {
public MultipleSourcesContainer dataSupplier(LinkedHashSet<String> sourceNames) {
return Fabric8ConfigUtils.secretsDataByName(context.client(), context.namespace(), sourceNames,
context.environment());
}

View File

@@ -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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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"));

View File

@@ -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}"
* </pre>
*/
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<String, String> shapeConfigMapK8s = Collections.singletonMap("six", "6");
createConfigMap("green-configmap-k8s", shapeConfigMapK8s, Collections.singletonMap("color", "green-k8s"));
Map<String, String> 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<String, String> shapeConfigMapProd = Collections.singletonMap("seven", "7");
createConfigMap("green-configmap-prod", shapeConfigMapProd, Collections.singletonMap("color", "green-prod"));
Map<String, String> greenConfigMapProd = Collections.singletonMap("seven", "7");
createConfigMap("green-configmap-prod", greenConfigMapProd, Collections.singletonMap("color", "green-prod"));
// not taken
Map<String, String> redConfigMap = Collections.singletonMap("three", "3");
@@ -87,6 +92,14 @@ abstract class LabeledConfigMapWithProfileTests {
Map<String, String> yellowConfigMap = Collections.singletonMap("four", "4");
createConfigMap("yellow-configmap", yellowConfigMap, Collections.singletonMap("color", "not-yellow"));
// is found by labels
Map<String, String> greenPurple = Collections.singletonMap("eight", "8");
createConfigMap("green-purple-configmap", greenPurple, Map.of("color", "green", "shape", "round"));
// is taken and thus overrides the above
Map<String, String> greenPurpleK8s = Collections.singletonMap("eight", "eight-ish");
createConfigMap("green-purple-configmap-k8s", greenPurpleK8s, Map.of("color", "black"));
}
private static void createConfigMap(String name, Map<String, String> data, Map<String, String> labels) {
@@ -109,15 +122,16 @@ abstract class LabeledConfigMapWithProfileTests {
/**
* <pre>
* 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.
* </pre>
*/
@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"));
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -40,15 +40,21 @@ abstract class LabeledSecretWithProfileTests {
@Autowired
private WebTestClient webClient;
/*
* <pre> - 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}" </pre>
/**
* <pre>
* - 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}"
* </pre>
*/
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<String, String> 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<String, String> 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<String, String> data, Map<String, String> labels) {
@@ -117,15 +133,16 @@ abstract class LabeledSecretWithProfileTests {
/**
* <pre>
* 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.
* </pre>
*/
@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"));
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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<String, String> one = Collections.singletonMap("one.property", "one");
Map<String, String> oneFromKubernetesProfile = Collections.singletonMap("one.property", "one-from-k8s");

View File

@@ -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<String, String> one = Collections.singletonMap("one.property",
Base64.getEncoder().encodeToString("one".getBytes(StandardCharsets.UTF_8)));
Map<String, String> oneFromKubernetesProfile = Collections.singletonMap("one.property",