labels, profiles, prefix, single yaml/properties for secrets and config maps (#1026)

This commit is contained in:
erabii
2022-06-08 17:15:54 +03:00
committed by GitHub
parent 75b4f802fc
commit 0e1d03dfcb
173 changed files with 7734 additions and 1145 deletions

View File

@@ -23,7 +23,10 @@ The link:https://github.com/spring-cloud/spring-cloud-kubernetes/tree/master/spr
during application startup and triggers hot reloading of beans or Spring context when changes are detected on
observed `ConfigMap` instances.
The default behavior is to create a `Fabric8ConfigMapPropertySource` based on a Kubernetes `ConfigMap` that has a `metadata.name` value of either the name of
Everything that follows is explained mainly referring to examples using ConfigMaps, but the same stands for
Secrets, i.e.: every feature is supported for both.
The default behavior is to create a `Fabric8ConfigMapPropertySource` (or a `KubernetesClientConfigMapPropertySource`) based on a Kubernetes `ConfigMap` that has a `metadata.name` value of either the name of
your Spring application (as defined by its `spring.application.name` property) or a custom name defined within the
`application.properties` file under the following key: `spring.cloud.kubernetes.config.name`.
@@ -135,6 +138,31 @@ data:
----
====
You can also define the search to happen based on labels, for example:
====
[source,yaml]
----
spring:
application:
name: labeled-configmap-with-prefix
cloud:
kubernetes:
config:
enableApi: true
useNameAsPrefix: true
namespace: spring-k8s
sources:
- labels:
letter: a
----
====
This will search for every configmap in namespace `spring-k8s` that has labels `{letter : a}`. The important
thing to notice here is that unlike reading a configmap by name, this can result in _multiple_ config maps read.
As usual, the same feature is supported for secrets.
You can also configure Spring Boot applications differently depending on active profiles that are merged together
when the `ConfigMap` is read. You can provide different property values for different profiles by using an
`application.properties` or `application.yaml` property, specifying profile-specific values, each in their own document

View File

@@ -19,24 +19,22 @@ package org.springframework.cloud.kubernetes.client.config;
import java.util.EnumMap;
import java.util.Optional;
import org.springframework.cloud.kubernetes.commons.config.ConfigMapPropertySource;
import org.springframework.cloud.kubernetes.commons.config.NormalizedSourceType;
import org.springframework.cloud.kubernetes.commons.config.SourceData;
import org.springframework.cloud.kubernetes.commons.config.SourceDataEntriesProcessor;
/**
* @author Ryan Baxter
* @author Isik Erhan
*/
public class KubernetesClientConfigMapPropertySource extends ConfigMapPropertySource {
public class KubernetesClientConfigMapPropertySource extends SourceDataEntriesProcessor {
private static final EnumMap<NormalizedSourceType, KubernetesClientContextToSourceData> STRATEGIES = new EnumMap<>(
NormalizedSourceType.class);
// there is a single strategy here at the moment (unlike secrets),
// but this can change.
// to be on par with secrets implementation, I am keeping it the same
static {
STRATEGIES.put(NormalizedSourceType.NAMED_CONFIG_MAP, namedConfigMap());
STRATEGIES.put(NormalizedSourceType.LABELED_CONFIG_MAP, labeledConfigMap());
}
public KubernetesClientConfigMapPropertySource(KubernetesClientConfigContext context) {
@@ -49,10 +47,12 @@ public class KubernetesClientConfigMapPropertySource extends ConfigMapPropertySo
.orElseThrow(() -> new IllegalArgumentException("no strategy found for : " + type));
}
// we need to pass various functions because the code we are interested in
// is protected in ConfigMapPropertySource, and must stay that way.
private static KubernetesClientContextToSourceData namedConfigMap() {
return NamedConfigMapContextToSourceDataProvider.of(ConfigMapPropertySource::processAllEntries).get();
return new NamedConfigMapContextToSourceDataProvider().get();
}
private static KubernetesClientContextToSourceData labeledConfigMap() {
return new LabeledConfigMapContextToSourceDataProvider().get();
}
}

View File

@@ -16,17 +16,24 @@
package org.springframework.cloud.kubernetes.client.config;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import io.kubernetes.client.openapi.models.V1ConfigMap;
import io.kubernetes.client.openapi.models.V1Secret;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
import org.springframework.cloud.kubernetes.commons.config.ConfigUtils;
import org.springframework.cloud.kubernetes.commons.config.MultipleSourcesContainer;
import org.springframework.cloud.kubernetes.commons.config.NamespaceResolutionFailedException;
import org.springframework.util.CollectionUtils;
import org.springframework.cloud.kubernetes.commons.config.StrippedSourceContainer;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
/**
@@ -36,6 +43,10 @@ public final class KubernetesClientConfigUtils {
private static final Log LOG = LogFactory.getLog(KubernetesClientConfigUtils.class);
// k8s-native client already returns data from secrets as being decoded
// this flags makes sure we use it everywhere
private static final boolean DECODE = Boolean.FALSE;
private KubernetesClientConfigUtils() {
}
@@ -78,23 +89,123 @@ public final class KubernetesClientConfigUtils {
}
/**
* return decoded data from a secret within a namespace.
* <pre>
* 1. read all secrets in the provided namespace
* 2. from the above, filter the ones that we care about (filter by labels)
* 3. with secret names from (2), find out if there are any profile based secrets (if profiles is not empty)
* 4. concat (2) and (3) and these are the secrets we are interested in
* 5. see if any of the secrets from (4) has a single yaml/properties file
* 6. gather all the names of the secrets (from 4) + data they hold
* </pre>
*/
static Map<String, Object> dataFromSecret(V1Secret secret, String namespace) {
LOG.debug("reading secret with name : " + secret.getMetadata().getName() + " in namespace : " + namespace);
Map<String, byte[]> data = secret.getData();
static MultipleSourcesContainer secretsDataByLabels(CoreV1Api client, String namespace, Map<String, String> labels,
Environment environment, Set<String> profiles) {
List<V1Secret> secrets = secretsSearch(client, namespace);
if (ConfigUtils.noSources(secrets, namespace)) {
return MultipleSourcesContainer.empty();
}
Map<String, Object> result = new HashMap<>(CollectionUtils.newHashMap(data.size()));
data.forEach((k, v) -> {
String decodedValue = decoded(v);
result.put(k, decodedValue);
});
List<StrippedSourceContainer> strippedSources = strippedSecrets(secrets);
return ConfigUtils.processLabeledData(strippedSources, environment, labels, namespace, profiles, DECODE);
return result;
}
private static String decoded(byte[] value) {
return new String(Base64.getDecoder().decode(Base64.getEncoder().encodeToString(value))).trim();
/**
* <pre>
* 1. read all config maps in the provided namespace
* 2. from the above, filter the ones that we care about (filter by labels)
* 3. with config maps names from (2), find out if there are any profile based ones (if profiles is not empty)
* 4. concat (2) and (3) and these are the config maps we are interested in
* 5. see if any from (4) has a single yaml/properties file
* 6. gather all the names of the config maps (from 4) + data they hold
* </pre>
*/
static MultipleSourcesContainer configMapsDataByLabels(CoreV1Api client, String namespace,
Map<String, String> labels, Environment environment, Set<String> profiles) {
List<V1ConfigMap> configMaps = configMapsSearch(client, namespace);
if (ConfigUtils.noSources(configMaps, namespace)) {
return MultipleSourcesContainer.empty();
}
List<StrippedSourceContainer> strippedSources = strippedConfigMaps(configMaps);
return ConfigUtils.processLabeledData(strippedSources, environment, labels, namespace, profiles, DECODE);
}
/**
* <pre>
* 1. read all secrets in the provided namespace
* 2. from the above, filter the ones that we care about (by name)
* 3. see if any of the secrets has a single yaml/properties file
* 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) {
List<V1Secret> secrets = secretsSearch(client, namespace);
if (ConfigUtils.noSources(secrets, namespace)) {
return MultipleSourcesContainer.empty();
}
List<StrippedSourceContainer> strippedSources = strippedSecrets(secrets);
return ConfigUtils.processNamedData(strippedSources, environment, sourceNames, namespace, DECODE);
}
/**
* <pre>
* 1. read all config maps in the provided namespace
* 2. from the above, filter the ones that we care about (by name)
* 3. see if any of the config maps has a single yaml/properties file
* 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) {
List<V1ConfigMap> configMaps = configMapsSearch(client, namespace);
if (ConfigUtils.noSources(configMaps, namespace)) {
return MultipleSourcesContainer.empty();
}
List<StrippedSourceContainer> strippedSources = strippedConfigMaps(configMaps);
return ConfigUtils.processNamedData(strippedSources, environment, sourceNames, namespace, DECODE);
}
private static List<V1Secret> secretsSearch(CoreV1Api client, String namespace) {
LOG.debug("Loading all secrets in namespace '" + namespace + "'");
try {
return client.listNamespacedSecret(namespace, null, null, null, null, null, null, null, null, null, null)
.getItems();
}
catch (ApiException apiException) {
throw new RuntimeException(apiException.getResponseBody(), apiException);
}
}
private static List<V1ConfigMap> configMapsSearch(CoreV1Api client, String namespace) {
LOG.debug("Loading all config maps in namespace '" + namespace + "'");
try {
return client.listNamespacedConfigMap(namespace, null, null, null, null, null, null, null, null, null, null)
.getItems();
}
catch (ApiException apiException) {
throw new RuntimeException(apiException.getResponseBody(), apiException);
}
}
private static List<StrippedSourceContainer> strippedSecrets(List<V1Secret> secrets) {
return secrets.stream().map(secret -> new StrippedSourceContainer(secret.getMetadata().getLabels(),
secret.getMetadata().getName(), transform(secret.getData()))).collect(Collectors.toList());
}
private static List<StrippedSourceContainer> strippedConfigMaps(List<V1ConfigMap> configMaps) {
return configMaps.stream().map(configMap -> new StrippedSourceContainer(configMap.getMetadata().getLabels(),
configMap.getMetadata().getName(), configMap.getData())).collect(Collectors.toList());
}
private static Map<String, String> transform(Map<String, byte[]> in) {
return in.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, en -> new String(en.getValue())));
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import org.springframework.cloud.kubernetes.commons.config.LabeledConfigMapNormalizedSource;
import org.springframework.cloud.kubernetes.commons.config.LabeledSourceData;
import org.springframework.cloud.kubernetes.commons.config.MultipleSourcesContainer;
class LabeledConfigMapContextToSourceDataProvider implements Supplier<KubernetesClientContextToSourceData> {
LabeledConfigMapContextToSourceDataProvider() {
}
/*
* Computes a ContextSourceData (think content) for configmap(s) based on some labels.
* There could be many sources that are read based on incoming labels, for which we
* will be computing a single Map<String, Object> in the end.
*
* If there is no config maps found for the provided labels, we will return an "empty"
* SourceData. Its name is going to be the concatenated labels mapped to an empty Map.
*
* If we find config maps(s) for the provided labels, its name is going to be the
* concatenated names mapped to the data they hold as a Map.
*/
@Override
public KubernetesClientContextToSourceData get() {
return context -> {
LabeledConfigMapNormalizedSource source = (LabeledConfigMapNormalizedSource) context.normalizedSource();
return new LabeledSourceData() {
@Override
public MultipleSourcesContainer dataSupplier(Map<String, String> labels, Set<String> profiles) {
return KubernetesClientConfigUtils.configMapsDataByLabels(context.client(), context.namespace(),
labels, context.environment(), profiles);
}
}.compute(source.labels(), source.prefix(), source.target(), source.profileSpecificSources(),
source.failFast(), context.namespace(), context.environment().getActiveProfiles());
};
}
}

View File

@@ -16,27 +16,13 @@
package org.springframework.cloud.kubernetes.client.config;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import io.kubernetes.client.openapi.models.V1ObjectMeta;
import io.kubernetes.client.openapi.models.V1Secret;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.kubernetes.commons.config.ConfigUtils;
import org.springframework.cloud.kubernetes.commons.config.LabeledSecretNormalizedSource;
import org.springframework.cloud.kubernetes.commons.config.PrefixContext;
import org.springframework.cloud.kubernetes.commons.config.SourceData;
import static org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigUtils.dataFromSecret;
import static org.springframework.cloud.kubernetes.commons.config.ConfigUtils.onException;
import static org.springframework.cloud.kubernetes.commons.config.Constants.PROPERTY_SOURCE_NAME_SEPARATOR;
import org.springframework.cloud.kubernetes.commons.config.LabeledSourceData;
import org.springframework.cloud.kubernetes.commons.config.MultipleSourcesContainer;
/**
* Provides an implementation of {@link KubernetesClientContextToSourceData} for a labeled
@@ -46,8 +32,6 @@ import static org.springframework.cloud.kubernetes.commons.config.Constants.PROP
*/
final class LabeledSecretContextToSourceDataProvider implements Supplier<KubernetesClientContextToSourceData> {
private static final Log LOG = LogFactory.getLog(LabeledSecretContextToSourceDataProvider.class);
LabeledSecretContextToSourceDataProvider() {
}
@@ -67,75 +51,18 @@ final class LabeledSecretContextToSourceDataProvider implements Supplier<Kuberne
public KubernetesClientContextToSourceData get() {
return context -> {
Map<String, Object> result = new HashMap<>();
LabeledSecretNormalizedSource source = (LabeledSecretNormalizedSource) context.normalizedSource();
Set<String> propertySourceNames = new LinkedHashSet<>();
Map<String, String> labels = source.labels();
String namespace = context.namespace();
String sourceNameFromLabels = String.join(PROPERTY_SOURCE_NAME_SEPARATOR, labels.keySet());
try {
LOG.info("Loading Secret with labels '" + labels + "' in namespace '" + namespace + "'");
List<V1Secret> secrets = context.client().listNamespacedSecret(namespace, null, null, null, null,
createLabelsSelector(labels), null, null, null, null, null).getItems();
if (!secrets.isEmpty()) {
for (V1Secret secret : secrets) {
// we support prefix per source, not per secret. This means that
// in theory
// we can still have clashes here, that we simply override.
// If there are more than one secret found per labels, and they
// have the same key on a
// property, but different values; one value will override the
// other, without any particular order.
result.putAll(dataFromSecret(secret, namespace));
}
String secretNames = secrets.stream().map(V1Secret::getMetadata).map(V1ObjectMeta::getName).sorted()
.collect(Collectors.joining(PROPERTY_SOURCE_NAME_SEPARATOR));
propertySourceNames.add(secretNames);
if (source.prefix() != ConfigUtils.Prefix.DEFAULT) {
String prefix;
if (source.prefix() == ConfigUtils.Prefix.KNOWN) {
prefix = source.prefix().prefixProvider().get();
}
else {
// prefix is going to be all the secret names we found based
// on the labels
// concatenated with PROPERTY_SOURCE_NAME_SEPARATOR
prefix = secretNames;
}
PrefixContext prefixContext = new PrefixContext(result, prefix, namespace, propertySourceNames);
return ConfigUtils.withPrefix(source.target(), prefixContext);
}
String names = String.join(PROPERTY_SOURCE_NAME_SEPARATOR, propertySourceNames);
return new SourceData(ConfigUtils.sourceName(source.target(), names, namespace), result);
return new LabeledSourceData() {
@Override
public MultipleSourcesContainer dataSupplier(Map<String, String> labels, Set<String> profiles) {
return KubernetesClientConfigUtils.secretsDataByLabels(context.client(), context.namespace(),
labels, context.environment(), profiles);
}
}
catch (Exception e) {
String message = "Unable to read Secret with labels [" + labels + "] in namespace '" + namespace + "'";
onException(source.failFast(), message, e);
}
// if we could not find a secret with provided labels, we will compute a
// response with an empty Map
// and name that will use all the label names (not their values)
String propertySourceNameFromLabels = ConfigUtils.sourceName(source.target(), sourceNameFromLabels,
namespace);
return new SourceData(propertySourceNameFromLabels, result);
}.compute(source.labels(), source.prefix(), source.target(), source.profileSpecificSources(),
source.failFast(), context.namespace(), context.environment().getActiveProfiles());
};
}
private static String createLabelsSelector(Map<String, String> labels) {
return labels.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining(","));
}
}

View File

@@ -16,29 +16,12 @@
package org.springframework.cloud.kubernetes.client.config;
import java.util.AbstractMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import io.kubernetes.client.openapi.ApiException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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.PrefixContext;
import org.springframework.cloud.kubernetes.commons.config.SourceData;
import org.springframework.core.env.Environment;
import static org.springframework.cloud.kubernetes.commons.config.ConfigUtils.onException;
import static org.springframework.cloud.kubernetes.commons.config.Constants.PROPERTY_SOURCE_NAME_SEPARATOR;
import org.springframework.cloud.kubernetes.commons.config.NamedSourceData;
/**
* Provides an implementation of {@link KubernetesClientContextToSourceData} for a named
@@ -48,18 +31,7 @@ import static org.springframework.cloud.kubernetes.commons.config.Constants.PROP
*/
final class NamedConfigMapContextToSourceDataProvider implements Supplier<KubernetesClientContextToSourceData> {
private static final Log LOG = LogFactory.getLog(NamedConfigMapContextToSourceDataProvider.class);
private final BiFunction<Map<String, String>, Environment, Map<String, Object>> entriesProcessor;
private NamedConfigMapContextToSourceDataProvider(
BiFunction<Map<String, String>, Environment, Map<String, Object>> entriesProcessor) {
this.entriesProcessor = Objects.requireNonNull(entriesProcessor);
}
static NamedConfigMapContextToSourceDataProvider of(
BiFunction<Map<String, String>, Environment, Map<String, Object>> entriesProcessor) {
return new NamedConfigMapContextToSourceDataProvider(entriesProcessor);
NamedConfigMapContextToSourceDataProvider() {
}
@Override
@@ -68,60 +40,15 @@ final class NamedConfigMapContextToSourceDataProvider implements Supplier<Kubern
return context -> {
NamedConfigMapNormalizedSource source = (NamedConfigMapNormalizedSource) context.normalizedSource();
// namespace has to be read from context, not from the normalized source
String namespace = context.namespace();
Environment environment = context.environment();
String configMapName = source.name().orElseThrow();
Set<String> propertySourceNames = new LinkedHashSet<>();
propertySourceNames.add(configMapName);
Map<String, Object> result = new HashMap<>();
try {
Set<String> names = new HashSet<>();
names.add(configMapName);
if (environment != null && source.profileSpecificSources()) {
for (String activeProfile : environment.getActiveProfiles()) {
names.add(configMapName + "-" + activeProfile);
}
return new NamedSourceData() {
@Override
public MultipleSourcesContainer dataSupplier(Set<String> sourceNames) {
return KubernetesClientConfigUtils.configMapsDataByName(context.client(), context.namespace(),
sourceNames, context.environment());
}
/*
* 1. read all config maps in the namespace 2. filter the ones that match
* user supplied name + profile specific ones 3. get an Entry that
* contains the data from config map + its name 4. create a single map
* that contains all the data from all config maps; also create a unified
* name of the property source (combined config map names).
*/
context.client()
.listNamespacedConfigMap(namespace, null, null, null, null, null, null, null, null, null, null)
.getItems().stream().filter(cm -> names.contains(cm.getMetadata().getName()))
.map(cm -> new AbstractMap.SimpleEntry<>(entriesProcessor.apply(cm.getData(), environment),
cm.getMetadata().getName()))
.collect(Collectors.toList()).forEach(entry -> {
LOG.info("Loaded config map with name : '" + entry.getValue() + "' in namespace : '"
+ namespace + "'");
result.putAll(entry.getKey());
propertySourceNames.add(entry.getValue());
});
if (source.prefix() != ConfigUtils.Prefix.DEFAULT) {
// since we are in a named source, calling get on the supplier is safe
String prefix = source.prefix().prefixProvider().get();
PrefixContext prefixContext = new PrefixContext(result, prefix, namespace, propertySourceNames);
return ConfigUtils.withPrefix(source.target(), prefixContext);
}
}
catch (ApiException e) {
// we could make the error tell exactly what config map we could not read,
// this would mean separate calls to kubeapi server via the client,
// though.
String message = "Unable to read ConfigMap(s) in namespace '" + namespace + "'";
onException(source.failFast(), message, e);
}
String propertySourceTokens = String.join(PROPERTY_SOURCE_NAME_SEPARATOR, propertySourceNames);
return new SourceData(ConfigUtils.sourceName(source.target(), propertySourceTokens, namespace), result);
}.compute(source.name().orElseThrow(), source.prefix(), source.target(), source.profileSpecificSources(),
source.failFast(), context.namespace(), context.environment().getActiveProfiles());
};
}

View File

@@ -16,24 +16,12 @@
package org.springframework.cloud.kubernetes.client.config;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import io.kubernetes.client.openapi.models.V1Secret;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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.PrefixContext;
import org.springframework.cloud.kubernetes.commons.config.SourceData;
import static org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigUtils.dataFromSecret;
import static org.springframework.cloud.kubernetes.commons.config.ConfigUtils.onException;
import org.springframework.cloud.kubernetes.commons.config.NamedSourceData;
/**
* Provides an implementation of {@link KubernetesClientContextToSourceData} for a named
@@ -43,8 +31,6 @@ import static org.springframework.cloud.kubernetes.commons.config.ConfigUtils.on
*/
final class NamedSecretContextToSourceDataProvider implements Supplier<KubernetesClientContextToSourceData> {
private static final Log LOG = LogFactory.getLog(NamedSecretContextToSourceDataProvider.class);
NamedSecretContextToSourceDataProvider() {
}
@@ -53,41 +39,15 @@ final class NamedSecretContextToSourceDataProvider implements Supplier<Kubernete
return context -> {
NamedSecretNormalizedSource source = (NamedSecretNormalizedSource) context.normalizedSource();
Set<String> propertySourceNames = new LinkedHashSet<>();
propertySourceNames.add(source.name().orElseThrow());
Map<String, Object> result = new HashMap<>();
String namespace = context.namespace();
// error should never be thrown here, since we always expect a name
// explicit or implicit
String name = source.name().orElseThrow();
try {
LOG.info("Loading Secret with name '" + name + "' in namespace '" + namespace + "'");
Optional<V1Secret> secret;
secret = context.client()
.listNamespacedSecret(namespace, null, null, null, null, null, null, null, null, null, null)
.getItems().stream().filter(s -> name.equals(s.getMetadata().getName())).findFirst();
secret.ifPresent(s -> result.putAll(dataFromSecret(s, namespace)));
if (source.prefix() != ConfigUtils.Prefix.DEFAULT && !result.isEmpty()) {
// since we are in a named source, calling get on the supplier is safe
String prefix = source.prefix().prefixProvider().get();
PrefixContext prefixContext = new PrefixContext(result, prefix, namespace, propertySourceNames);
return ConfigUtils.withPrefix(source.target(), prefixContext);
return new NamedSourceData() {
@Override
public MultipleSourcesContainer dataSupplier(Set<String> sourceNames) {
return KubernetesClientConfigUtils.secretsDataByName(context.client(), context.namespace(),
sourceNames, context.environment());
}
}
catch (Exception e) {
String message = "Unable to read Secret with name '" + name + "' in namespace '" + namespace + "'";
onException(source.failFast(), message, e);
}
String propertySourceName = ConfigUtils.sourceName(source.target(), name, namespace);
return new SourceData(propertySourceName, result);
}.compute(source.name().orElseThrow(), source.prefix(), source.target(), source.profileSpecificSources(),
source.failFast(), context.namespace(), context.environment().getActiveProfiles());
};
}

View File

@@ -1,43 +0,0 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.kubernetes.client.config.applications.include_profile_specific_sources.IncludeProfileSpecificSourcesApp;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit.jupiter.SpringExtension;
/**
* The stub data for this test is in : IncludeProfileSpecificSourcesConfigurationStub
*
* @author wind57
*/
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = IncludeProfileSpecificSourcesApp.class,
properties = { "spring.cloud.bootstrap.name=include-profile-specific-sources",
"include.profile.specific.sources=true", "spring.main.cloud-platform=KUBERNETES",
"spring.cloud.bootstrap.enabled=true" })
@AutoConfigureWebTestClient
@ActiveProfiles("dev")
class KubernetesClientConfigMapBootstrapIncludeProfileSpecificSourcesTests
extends KubernetesClientConfigMapIncludeProfileSpecificSourcesTests {
}

View File

@@ -183,7 +183,7 @@ class KubernetesClientConfigMapPropertySourceLocatorTests {
configMapConfigProperties, new KubernetesNamespaceProvider(new MockEnvironment()));
assertThatThrownBy(() -> locator.locate(new MockEnvironment())).isInstanceOf(IllegalStateException.class)
.hasMessage("Unable to read ConfigMap(s) in namespace 'default'");
.hasMessage("Internal Server Error");
}
@Test

View File

@@ -182,8 +182,7 @@ class KubernetesClientConfigMapPropertySourceTests {
new MockEnvironment());
assertThatThrownBy(() -> new KubernetesClientConfigMapPropertySource(context))
.isInstanceOf(IllegalStateException.class)
.hasMessage("Unable to read ConfigMap(s) in namespace 'default'");
.isInstanceOf(IllegalStateException.class).hasMessage("Internal Server Error");
verify(getRequestedFor(urlEqualTo("/api/v1/namespaces/default/configmaps")));
}

View File

@@ -174,7 +174,7 @@ class KubernetesClientSecretsPropertySourceLocatorTests {
new KubernetesNamespaceProvider(new MockEnvironment()), secretsConfigProperties);
assertThatThrownBy(() -> locator.locate(new MockEnvironment())).isInstanceOf(IllegalStateException.class)
.hasMessage("Unable to read Secret with name 'db-secret' in namespace 'default'");
.hasMessage("Internal Server Error");
}
@Test

View File

@@ -64,7 +64,7 @@ class KubernetesClientSecretsPropertySourceTests {
.withNamespace("default").build())
.addToData("password", "p455w0rd".getBytes()).addToData("username", "user".getBytes()).build()).build();
private static final String LIST_API_WITH_LABEL = "/api/v1/namespaces/default/secrets?labelSelector=spring.cloud.kubernetes.secret%3Dtrue";
private static final String LIST_API_WITH_LABEL = "/api/v1/namespaces/default/secrets";
private static final String LIST_BODY = "{\n" + "\t\"kind\": \"SecretList\",\n" + "\t\"apiVersion\": \"v1\",\n"
+ "\t\"metadata\": {\n" + "\t\t\"selfLink\": \"/api/v1/secrets\",\n"
@@ -119,7 +119,7 @@ class KubernetesClientSecretsPropertySourceTests {
CoreV1Api api = new CoreV1Api();
stubFor(get(API).willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(SECRET_LIST))));
NormalizedSource source = new NamedSecretNormalizedSource("db-secret", "default", false);
NormalizedSource source = new NamedSecretNormalizedSource("db-secret", "default", false, false);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, "default",
new MockEnvironment());
@@ -137,7 +137,7 @@ class KubernetesClientSecretsPropertySourceTests {
Map<String, String> labels = new HashMap<>();
labels.put("spring.cloud.kubernetes.secret", "true");
NormalizedSource source = new LabeledSecretNormalizedSource("default", labels, false);
NormalizedSource source = new LabeledSecretNormalizedSource("default", labels, false, false);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, "default",
new MockEnvironment());
@@ -151,13 +151,12 @@ class KubernetesClientSecretsPropertySourceTests {
CoreV1Api api = new CoreV1Api();
stubFor(get(API).willReturn(aResponse().withStatus(500).withBody("Internal Server Error")));
NormalizedSource source = new NamedSecretNormalizedSource("secret", "default", true);
NormalizedSource source = new NamedSecretNormalizedSource("secret", "default", true, false);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, "default",
new MockEnvironment());
assertThatThrownBy(() -> new KubernetesClientSecretsPropertySource(context))
.isInstanceOf(IllegalStateException.class)
.hasMessage("Unable to read Secret with name 'secret' in namespace 'default'");
.isInstanceOf(IllegalStateException.class).hasMessage("Internal Server Error");
verify(getRequestedFor(urlEqualTo(API)));
}
@@ -166,7 +165,7 @@ class KubernetesClientSecretsPropertySourceTests {
CoreV1Api api = new CoreV1Api();
stubFor(get(API).willReturn(aResponse().withStatus(500).withBody("Internal Server Error")));
NormalizedSource source = new NamedSecretNormalizedSource("secret", "db-secret", false);
NormalizedSource source = new NamedSecretNormalizedSource("secret", "db-secret", false, false);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, "default",
new MockEnvironment());

View File

@@ -0,0 +1,453 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.Configuration;
import io.kubernetes.client.openapi.JSON;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import io.kubernetes.client.openapi.models.V1ConfigMap;
import io.kubernetes.client.openapi.models.V1ConfigMapBuilder;
import io.kubernetes.client.openapi.models.V1ConfigMapList;
import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder;
import io.kubernetes.client.util.ClientBuilder;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.cloud.kubernetes.commons.config.ConfigUtils;
import org.springframework.cloud.kubernetes.commons.config.LabeledConfigMapNormalizedSource;
import org.springframework.cloud.kubernetes.commons.config.NormalizedSource;
import org.springframework.cloud.kubernetes.commons.config.SourceData;
import org.springframework.mock.env.MockEnvironment;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
/**
* @author wind57
*/
class LabeledConfigMapContextToSourceDataProviderTests {
private static final Map<String, String> LABELS = new LinkedHashMap<>();
private static final Map<String, String> RED_LABEL = Map.of("color", "red");
private static final Map<String, String> BLUE_LABEL = Map.of("color", "blue");
private static final Map<String, String> PINK_LABEL = Map.of("color", "pink");
private static final String NAMESPACE = "default";
static {
LABELS.put("label2", "value2");
LABELS.put("label1", "value1");
}
@BeforeAll
static void setup() {
WireMockServer wireMockServer = new WireMockServer(options().dynamicPort());
wireMockServer.start();
WireMock.configureFor("localhost", wireMockServer.port());
ApiClient client = new ClientBuilder().setBasePath("http://localhost:" + wireMockServer.port()).build();
client.setDebugging(true);
Configuration.setDefaultApiClient(client);
}
@AfterEach
void afterEach() {
WireMock.reset();
}
/**
* we have a single config map deployed. it has two labels and these match against our
* queries.
*/
@Test
void singleConfigMapMatchAgainstLabels() {
V1ConfigMap one = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder().withName("test-configmap")
.withLabels(LABELS).withNamespace(NAMESPACE).build()).addToData("name", "value").build();
V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(one);
stubCall(configMapList);
CoreV1Api api = new CoreV1Api();
NormalizedSource source = new LabeledConfigMapNormalizedSource(NAMESPACE, LABELS, true, false);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());
KubernetesClientContextToSourceData data = new LabeledConfigMapContextToSourceDataProvider().get();
SourceData sourceData = data.apply(context);
Assertions.assertEquals("configmap.test-configmap.default", sourceData.sourceName());
Assertions.assertEquals(Map.of("name", "value"), sourceData.sourceData());
}
/**
* we have three configmaps deployed. two of them have labels that match (color=red),
* one does not (color=blue).
*/
@Test
void twoConfigMapsMatchAgainstLabels() {
V1ConfigMap redOne = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder().withName("red-configmap")
.withLabels(RED_LABEL).withNamespace(NAMESPACE).build()).addToData("colorOne", "really-red").build();
V1ConfigMap redTwo = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder()
.withName("red-configmap-again").withLabels(RED_LABEL).withNamespace(NAMESPACE).build())
.addToData("colorTwo", "really-red-again").build();
V1ConfigMap blue = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder().withName("blue-configmap")
.withLabels(BLUE_LABEL).withNamespace(NAMESPACE).build()).addToData("color", "blue").build();
V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(redOne).addItemsItem(redTwo)
.addItemsItem(blue);
stubCall(configMapList);
CoreV1Api api = new CoreV1Api();
NormalizedSource source = new LabeledConfigMapNormalizedSource(NAMESPACE, RED_LABEL, true, false);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());
KubernetesClientContextToSourceData data = new LabeledConfigMapContextToSourceDataProvider().get();
SourceData sourceData = data.apply(context);
Assertions.assertEquals(sourceData.sourceName(), "configmap.red-configmap.red-configmap-again.default");
Assertions.assertEquals(sourceData.sourceData().size(), 2);
Assertions.assertEquals(sourceData.sourceData().get("colorOne"), "really-red");
Assertions.assertEquals(sourceData.sourceData().get("colorTwo"), "really-red-again");
}
/**
* one configmap deployed (pink), does not match our query (blue).
*/
@Test
void configMapNoMatch() {
V1ConfigMap one = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder().withName("pink-configmap")
.withLabels(PINK_LABEL).withNamespace(NAMESPACE).build()).addToData("color", "pink").build();
V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(one);
stubCall(configMapList);
CoreV1Api api = new CoreV1Api();
NormalizedSource source = new LabeledConfigMapNormalizedSource(NAMESPACE, BLUE_LABEL, true, false);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());
KubernetesClientContextToSourceData data = new LabeledConfigMapContextToSourceDataProvider().get();
SourceData sourceData = data.apply(context);
Assertions.assertEquals(sourceData.sourceName(), "configmap.color.default");
Assertions.assertEquals(sourceData.sourceData(), Collections.emptyMap());
}
/**
* LabeledConfigMapContextToSourceDataProvider gets as input a Fabric8ConfigContext.
* This context has a namespace as well as a NormalizedSource, that has a namespace
* too. It is easy to get confused in code on which namespace to use. This test makes
* sure that we use the proper one.
*/
@Test
void namespaceMatch() {
V1ConfigMap one = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder().withName("test-configmap")
.withLabels(LABELS).withNamespace(NAMESPACE).build()).addToData("name", "value").build();
V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(one);
stubCall(configMapList);
CoreV1Api api = new CoreV1Api();
String wrongNamespace = NAMESPACE + "nope";
NormalizedSource source = new LabeledConfigMapNormalizedSource(wrongNamespace, LABELS, true, false);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());
KubernetesClientContextToSourceData data = new LabeledConfigMapContextToSourceDataProvider().get();
SourceData sourceData = data.apply(context);
Assertions.assertEquals("configmap.test-configmap.default", sourceData.sourceName());
Assertions.assertEquals(Map.of("name", "value"), sourceData.sourceData());
}
/**
* one configmap with name : "blue-configmap" and labels "color=blue" is deployed. we
* search it with the same labels, find it, and assert that name of the SourceData (it
* must use its name, not its labels) and values in the SourceData must be prefixed
* (since we have provided an explicit prefix).
*/
@Test
void testWithPrefix() {
V1ConfigMap one = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder().withName("blue-configmap")
.withLabels(BLUE_LABEL).withNamespace(NAMESPACE).build()).addToData("what-color", "blue-color").build();
V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(one);
stubCall(configMapList);
CoreV1Api api = new CoreV1Api();
ConfigUtils.Prefix mePrefix = ConfigUtils.findPrefix("me", false, false, "irrelevant");
NormalizedSource source = new LabeledConfigMapNormalizedSource(NAMESPACE, BLUE_LABEL, true, mePrefix, false);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());
KubernetesClientContextToSourceData data = new LabeledConfigMapContextToSourceDataProvider().get();
SourceData sourceData = data.apply(context);
Assertions.assertEquals("configmap.blue-configmap.default", sourceData.sourceName());
Assertions.assertEquals(Map.of("me.what-color", "blue-color"), sourceData.sourceData());
}
/**
* two configmaps are deployed (name:blue-configmap, name:another-blue-configmap) and
* labels "color=blue" (on both). we search with the same labels, find them, and
* assert that name of the SourceData (it must use its name, not its labels) and
* values in the SourceData must be prefixed (since we have provided a delayed
* prefix).
*
* Also notice that the prefix is made up from both configmap names.
*
*/
@Test
void testTwoConfigmapsWithPrefix() {
V1ConfigMap one = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder().withName("blue-configmap")
.withLabels(BLUE_LABEL).withNamespace(NAMESPACE).build()).addToData("first", "blue").build();
V1ConfigMap two = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder()
.withName("another-blue-configmap").withLabels(BLUE_LABEL).withNamespace(NAMESPACE).build())
.addToData("second", "blue").build();
V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(one).addItemsItem(two);
stubCall(configMapList);
CoreV1Api api = new CoreV1Api();
NormalizedSource source = new LabeledConfigMapNormalizedSource(NAMESPACE, BLUE_LABEL, true,
ConfigUtils.Prefix.DELAYED, false);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());
KubernetesClientContextToSourceData data = new LabeledConfigMapContextToSourceDataProvider().get();
SourceData sourceData = data.apply(context);
Assertions.assertEquals(sourceData.sourceName(), "configmap.another-blue-configmap.blue-configmap.default");
Map<String, Object> properties = sourceData.sourceData();
Assertions.assertEquals(2, properties.size());
Iterator<String> keys = properties.keySet().iterator();
String firstKey = keys.next();
String secondKey = keys.next();
if (firstKey.contains("first")) {
Assertions.assertEquals(firstKey, "another-blue-configmap.blue-configmap.first");
}
Assertions.assertEquals(secondKey, "another-blue-configmap.blue-configmap.second");
Assertions.assertEquals(properties.get(firstKey), "blue");
Assertions.assertEquals(properties.get(secondKey), "blue");
}
/**
* two configmaps are deployed: "color-configmap" with label: "{color:blue}" and
* "color-configmap-k8s" with no labels. We search by "{color:red}", do not find
* anything and thus have an empty SourceData. profile based sources are enabled, but
* it has no effect.
*/
@Test
void searchWithLabelsNoConfigmapsFound() {
V1ConfigMap one = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder().withName("color-configmap")
.withLabels(BLUE_LABEL).withNamespace(NAMESPACE).build()).addToData("one", "1").build();
V1ConfigMap two = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("color-config-k8s").withNamespace(NAMESPACE).build())
.addToData("two", "2").build();
V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(one).addItemsItem(two);
stubCall(configMapList);
CoreV1Api api = new CoreV1Api();
NormalizedSource source = new LabeledConfigMapNormalizedSource(NAMESPACE, RED_LABEL, true,
ConfigUtils.Prefix.DEFAULT, true);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());
KubernetesClientContextToSourceData data = new LabeledConfigMapContextToSourceDataProvider().get();
SourceData sourceData = data.apply(context);
Assertions.assertTrue(sourceData.sourceData().isEmpty());
Assertions.assertEquals(sourceData.sourceName(), "configmap.color.default");
}
/**
* two configmaps are deployed: "color-configmap" with label: "{color:blue}" and
* "shape-configmap" with label: "{shape:round}". We search by "{color:blue}" and find
* one configmap. profile based sources are enabled, but it has no effect.
*/
@Test
void searchWithLabelsOneConfigMapFound() {
V1ConfigMap one = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder().withName("color-configmap")
.withLabels(BLUE_LABEL).withNamespace(NAMESPACE).build()).addToData("one", "1").build();
V1ConfigMap two = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("shape-configmap").withNamespace(NAMESPACE).build())
.addToData("two", "2").build();
V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(one).addItemsItem(two);
stubCall(configMapList);
CoreV1Api api = new CoreV1Api();
NormalizedSource source = new LabeledConfigMapNormalizedSource(NAMESPACE, BLUE_LABEL, true,
ConfigUtils.Prefix.DEFAULT, true);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());
KubernetesClientContextToSourceData data = new LabeledConfigMapContextToSourceDataProvider().get();
SourceData sourceData = data.apply(context);
Assertions.assertEquals(sourceData.sourceData().size(), 1);
Assertions.assertEquals(sourceData.sourceData().get("one"), "1");
Assertions.assertEquals(sourceData.sourceName(), "configmap.color-configmap.default");
}
/**
* two configmaps are deployed: "color-configmap" with label: "{color:blue}" and
* "color-configmap-k8s" with label: "{color:red}". We search by "{color:blue}" and
* find one configmap. Since profiles are enabled, we will also be reading
* "color-configmap-k8s", even if its labels do not match provided ones.
*/
@Test
void searchWithLabelsOneConfigMapFoundAndOneFromProfileFound() {
V1ConfigMap one = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder().withName("color-configmap")
.withLabels(BLUE_LABEL).withNamespace(NAMESPACE).build()).addToData("one", "1").build();
V1ConfigMap two = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder()
.withName("color-configmap-k8s").withLabels(RED_LABEL).withNamespace(NAMESPACE).build())
.addToData("two", "2").build();
V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(one).addItemsItem(two);
stubCall(configMapList);
CoreV1Api api = new CoreV1Api();
MockEnvironment environment = new MockEnvironment();
environment.setActiveProfiles("k8s");
NormalizedSource source = new LabeledConfigMapNormalizedSource(NAMESPACE, BLUE_LABEL, true,
ConfigUtils.Prefix.DELAYED, true);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, environment);
KubernetesClientContextToSourceData data = new LabeledConfigMapContextToSourceDataProvider().get();
SourceData sourceData = data.apply(context);
Assertions.assertEquals(sourceData.sourceData().size(), 2);
Assertions.assertEquals(sourceData.sourceData().get("color-configmap.color-configmap-k8s.one"), "1");
Assertions.assertEquals(sourceData.sourceData().get("color-configmap.color-configmap-k8s.two"), "2");
Assertions.assertEquals(sourceData.sourceName(), "configmap.color-configmap.color-configmap-k8s.default");
}
/**
* <pre>
* - configmap "color-configmap" with label "{color:blue}"
* - configmap "shape-configmap" with labels "{color:blue, shape:round}"
* - configmap "no-fit" with labels "{tag:no-fit}"
* - configmap "color-configmap-k8s" with label "{color:red}"
* - configmap "shape-configmap-k8s" with label "{shape:triangle}"
* </pre>
*/
@Test
void searchWithLabelsTwoConfigMapsFoundAndOneFromProfileFound() {
V1ConfigMap colorConfigMap = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder()
.withName("color-configmap").withLabels(BLUE_LABEL).withNamespace(NAMESPACE).build())
.addToData("one", "1").build();
V1ConfigMap shapeConfigmap = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("shape-configmap")
.withLabels(Map.of("color", "blue", "shape", "round")).withNamespace(NAMESPACE).build())
.addToData("two", "2").build();
V1ConfigMap noFit = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder().withName("no-fit")
.withLabels(Map.of("tag", "no-fit")).withNamespace(NAMESPACE).build()).addToData("three", "3").build();
V1ConfigMap colorConfigmapK8s = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder()
.withName("color-configmap-k8s").withLabels(RED_LABEL).withNamespace(NAMESPACE).build())
.addToData("four", "4").build();
V1ConfigMap shapeConfigmapK8s = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("shape-configmap-k8s")
.withLabels(Map.of("shape", "triangle")).withNamespace(NAMESPACE).build())
.addToData("five", "5").build();
V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(colorConfigMap).addItemsItem(shapeConfigmap)
.addItemsItem(noFit).addItemsItem(colorConfigmapK8s).addItemsItem(shapeConfigmapK8s);
stubCall(configMapList);
CoreV1Api api = new CoreV1Api();
MockEnvironment environment = new MockEnvironment();
environment.setActiveProfiles("k8s");
NormalizedSource source = new LabeledConfigMapNormalizedSource(NAMESPACE, BLUE_LABEL, true,
ConfigUtils.Prefix.DELAYED, true);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, environment);
KubernetesClientContextToSourceData data = new LabeledConfigMapContextToSourceDataProvider().get();
SourceData sourceData = data.apply(context);
Assertions.assertEquals(sourceData.sourceData().size(), 4);
Assertions.assertEquals(sourceData.sourceData()
.get("color-configmap.color-configmap-k8s.shape-configmap.shape-configmap-k8s.one"), "1");
Assertions.assertEquals(sourceData.sourceData()
.get("color-configmap.color-configmap-k8s.shape-configmap.shape-configmap-k8s.two"), "2");
Assertions.assertEquals(sourceData.sourceData()
.get("color-configmap.color-configmap-k8s.shape-configmap.shape-configmap-k8s.four"), "4");
Assertions.assertEquals(sourceData.sourceData()
.get("color-configmap.color-configmap-k8s.shape-configmap.shape-configmap-k8s.five"), "5");
Assertions.assertEquals(sourceData.sourceName(),
"configmap.color-configmap.color-configmap-k8s.shape-configmap.shape-configmap-k8s.default");
}
private void stubCall(V1ConfigMapList list) {
stubFor(get("/api/v1/namespaces/default/configmaps")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(list))));
}
}

View File

@@ -29,6 +29,7 @@ import io.kubernetes.client.openapi.Configuration;
import io.kubernetes.client.openapi.JSON;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder;
import io.kubernetes.client.openapi.models.V1Secret;
import io.kubernetes.client.openapi.models.V1SecretBuilder;
import io.kubernetes.client.openapi.models.V1SecretList;
import io.kubernetes.client.util.ClientBuilder;
@@ -87,18 +88,18 @@ class LabeledSecretContextToSourceDataProviderTests {
@Test
void noMatch() {
V1SecretList secretList = new V1SecretList().addItemsItem(new V1SecretBuilder()
V1Secret red = new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withLabels(Collections.singletonMap("color", "red"))
.withNamespace(NAMESPACE).withName("red-secret").withResourceVersion("1").build())
.addToData("color", Base64.getEncoder().encode("really-red".getBytes())).build());
.withNamespace(NAMESPACE).withName("red-secret").build())
.addToData("color", Base64.getEncoder().encode("really-red".getBytes())).build();
V1SecretList secretList = new V1SecretList().addItemsItem(red);
stubCall(secretList);
CoreV1Api api = new CoreV1Api();
stubFor(get("/api/v1/namespaces/default/secrets?labelSelector=color%3Dred")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(secretList))));
// blue does not match red
NormalizedSource source = new LabeledSecretNormalizedSource(NAMESPACE,
Collections.singletonMap("color", "blue"), false);
Collections.singletonMap("color", "blue"), false, false);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());
@@ -117,16 +118,15 @@ class LabeledSecretContextToSourceDataProviderTests {
@Test
void singleSecretMatchAgainstLabels() {
V1SecretList SECRETS_LIST = new V1SecretList().addItemsItem(new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withLabels(LABELS).withNamespace(NAMESPACE)
.withResourceVersion("1").withName("test-secret").build())
.addToData("color", "really-red".getBytes()).build());
V1Secret red = new V1SecretBuilder().withMetadata(
new V1ObjectMetaBuilder().withLabels(LABELS).withNamespace(NAMESPACE).withName("test-secret").build())
.addToData("color", "really-red".getBytes()).build();
V1SecretList secretList = new V1SecretList().addItemsItem(red);
stubCall(secretList);
CoreV1Api api = new CoreV1Api();
stubFor(get("/api/v1/namespaces/default/secrets?labelSelector=label2%3Dvalue2%2Clabel1%3Dvalue1")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(SECRETS_LIST))));
NormalizedSource source = new LabeledSecretNormalizedSource(NAMESPACE, LABELS, false);
NormalizedSource source = new LabeledSecretNormalizedSource(NAMESPACE, LABELS, false, false);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());
@@ -144,22 +144,19 @@ class LabeledSecretContextToSourceDataProviderTests {
@Test
void twoSecretsMatchAgainstLabels() {
V1SecretList secretList = new V1SecretList();
secretList.addItemsItem(new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withLabels(RED_LABEL).withNamespace(NAMESPACE)
.withResourceVersion("1").withName("color-one").build())
.addToData("colorOne", "really-red-one".getBytes()).build());
V1Secret one = new V1SecretBuilder().withMetadata(
new V1ObjectMetaBuilder().withLabels(RED_LABEL).withNamespace(NAMESPACE).withName("color-one").build())
.addToData("colorOne", "really-red-one".getBytes()).build();
secretList.addItemsItem(new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withLabels(RED_LABEL).withNamespace(NAMESPACE)
.withResourceVersion("1").withName("color-two").build())
.addToData("colorTwo", "really-red-two".getBytes()).build());
V1Secret two = new V1SecretBuilder().withMetadata(
new V1ObjectMetaBuilder().withLabels(RED_LABEL).withNamespace(NAMESPACE).withName("color-two").build())
.addToData("colorTwo", "really-red-two".getBytes()).build();
V1SecretList secretList = new V1SecretList().addItemsItem(one).addItemsItem(two);
stubCall(secretList);
CoreV1Api api = new CoreV1Api();
stubFor(get("/api/v1/namespaces/default/secrets?labelSelector=color%3Dred")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(secretList))));
NormalizedSource source = new LabeledSecretNormalizedSource(NAMESPACE, RED_LABEL, false);
NormalizedSource source = new LabeledSecretNormalizedSource(NAMESPACE, RED_LABEL, false, false);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());
@@ -175,16 +172,15 @@ class LabeledSecretContextToSourceDataProviderTests {
@Test
void namespaceMatch() {
V1SecretList SECRETS_LIST = new V1SecretList().addItemsItem(new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withLabels(LABELS).withNamespace(NAMESPACE)
.withResourceVersion("1").withName("test-secret").build())
.addToData("color", "really-red".getBytes()).build());
V1Secret one = new V1SecretBuilder().withMetadata(
new V1ObjectMetaBuilder().withLabels(LABELS).withNamespace(NAMESPACE).withName("test-secret").build())
.addToData("color", "really-red".getBytes()).build();
V1SecretList secretList = new V1SecretList().addItemsItem(one);
stubCall(secretList);
CoreV1Api api = new CoreV1Api();
stubFor(get("/api/v1/namespaces/default/secrets?labelSelector=label2%3Dvalue2%2Clabel1%3Dvalue1")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(SECRETS_LIST))));
NormalizedSource source = new LabeledSecretNormalizedSource(NAMESPACE + "nope", LABELS, false);
NormalizedSource source = new LabeledSecretNormalizedSource(NAMESPACE + "nope", LABELS, false, false);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());
@@ -204,17 +200,17 @@ class LabeledSecretContextToSourceDataProviderTests {
@Test
void testWithPrefix() {
V1SecretList SECRETS_LIST = new V1SecretList().addItemsItem(new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withLabels(Map.of("color", "blue")).withNamespace(NAMESPACE)
.withResourceVersion("1").withName("blue-secret").build())
.addToData("what-color", "blue-color".getBytes()).build());
V1Secret one = new V1SecretBuilder().withMetadata(new V1ObjectMetaBuilder().withLabels(Map.of("color", "blue"))
.withNamespace(NAMESPACE).withName("blue-secret").build())
.addToData("what-color", "blue-color".getBytes()).build();
V1SecretList secretList = new V1SecretList().addItemsItem(one);
stubCall(secretList);
CoreV1Api api = new CoreV1Api();
stubFor(get("/api/v1/namespaces/default/secrets?labelSelector=color%3Dblue")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(SECRETS_LIST))));
ConfigUtils.Prefix prefix = ConfigUtils.findPrefix("me", false, false, null);
NormalizedSource source = new LabeledSecretNormalizedSource(NAMESPACE, Map.of("color", "blue"), false, prefix);
NormalizedSource source = new LabeledSecretNormalizedSource(NAMESPACE, Map.of("color", "blue"), false, prefix,
false);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());
@@ -236,26 +232,22 @@ class LabeledSecretContextToSourceDataProviderTests {
*/
@Test
void testTwoSecretsWithPrefix() {
V1SecretList SECRETS_LIST = new V1SecretList()
.addItemsItem(
new V1SecretBuilder()
.withMetadata(
new V1ObjectMetaBuilder().withLabels(Map.of("color", "blue"))
.withNamespace(NAMESPACE).withResourceVersion("1").withName(
"blue-secret")
.build())
.addToData("first", "blue".getBytes()).build())
.addItemsItem(new V1SecretBuilder()
.withMetadata(
new V1ObjectMetaBuilder().withLabels(Map.of("color", "blue")).withNamespace(NAMESPACE)
.withResourceVersion("1").withName("another-blue-secret").build())
.addToData("second", "blue".getBytes()).build());
V1Secret one = new V1SecretBuilder().withMetadata(new V1ObjectMetaBuilder().withLabels(Map.of("color", "blue"))
.withNamespace(NAMESPACE).withName("blue-secret").build()).addToData("first", "blue".getBytes())
.build();
V1Secret two = new V1SecretBuilder().withMetadata(new V1ObjectMetaBuilder().withLabels(Map.of("color", "blue"))
.withNamespace(NAMESPACE).withName("another-blue-secret").build())
.addToData("second", "blue".getBytes()).build();
V1SecretList secretList = new V1SecretList().addItemsItem(one).addItemsItem(two);
stubCall(secretList);
CoreV1Api api = new CoreV1Api();
stubFor(get("/api/v1/namespaces/default/secrets?labelSelector=color%3Dblue")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(SECRETS_LIST))));
NormalizedSource source = new LabeledSecretNormalizedSource(NAMESPACE, Map.of("color", "blue"), false, ConfigUtils.Prefix.DELAYED);
NormalizedSource source = new LabeledSecretNormalizedSource(NAMESPACE, Map.of("color", "blue"), false,
ConfigUtils.Prefix.DELAYED, false);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());
@@ -284,4 +276,172 @@ class LabeledSecretContextToSourceDataProviderTests {
Assertions.assertEquals(properties.get(secondKey), "blue");
}
/**
* two secrets are deployed: secret "color-secret" with label: "{color:blue}" and
* "shape-secret" with label: "{shape:round}". We search by "{color:blue}" and find
* one secret. profile based sources are enabled, but it has no effect.
*/
@Test
void searchWithLabelsOneSecretFound() {
V1Secret colorSecret = new V1SecretBuilder().withMetadata(new V1ObjectMetaBuilder()
.withLabels(Map.of("color", "blue")).withNamespace(NAMESPACE).withName("color-secret").build())
.addToData("one", "1".getBytes()).build();
V1Secret shapeSecret = new V1SecretBuilder().withMetadata(new V1ObjectMetaBuilder()
.withLabels(Map.of("shape", "round")).withNamespace(NAMESPACE).withName("shape-secret").build())
.addToData("two", "2".getBytes()).build();
V1SecretList secretList = new V1SecretList().addItemsItem(colorSecret).addItemsItem(shapeSecret);
stubCall(secretList);
CoreV1Api api = new CoreV1Api();
NormalizedSource source = new LabeledSecretNormalizedSource(NAMESPACE, Map.of("color", "blue"), false,
ConfigUtils.Prefix.DEFAULT, true);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());
KubernetesClientContextToSourceData data = new LabeledSecretContextToSourceDataProvider().get();
SourceData sourceData = data.apply(context);
Assertions.assertEquals(sourceData.sourceData().size(), 1);
Assertions.assertEquals(sourceData.sourceData().get("one"), "1");
Assertions.assertEquals(sourceData.sourceName(), "secret.color-secret.default");
}
/**
* two secrets are deployed: secret "color-secret" with label: "{color:blue}" and
* "color-secret-k8s" with label: "{color:red}". We search by "{color:blue}" and find
* one secret. Since profiles are enabled, we will also be reading "color-secret-k8s",
* even if its labels do not match provided ones.
*/
@Test
void searchWithLabelsOneSecretFoundAndOneFromProfileFound() {
V1Secret colorSecret = new V1SecretBuilder().withMetadata(new V1ObjectMetaBuilder()
.withLabels(Map.of("color", "blue")).withNamespace(NAMESPACE).withName("color-secret").build())
.addToData("one", "1".getBytes()).build();
V1Secret shapeSecret = new V1SecretBuilder().withMetadata(new V1ObjectMetaBuilder()
.withLabels(Map.of("color", "red")).withNamespace(NAMESPACE).withName("color-secret-k8s").build())
.addToData("two", "2".getBytes()).build();
V1SecretList secretList = new V1SecretList().addItemsItem(colorSecret).addItemsItem(shapeSecret);
stubCall(secretList);
CoreV1Api api = new CoreV1Api();
MockEnvironment environment = new MockEnvironment();
environment.setActiveProfiles("k8s");
NormalizedSource source = new LabeledSecretNormalizedSource(NAMESPACE, Map.of("color", "blue"), false,
ConfigUtils.Prefix.DELAYED, true);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, environment);
KubernetesClientContextToSourceData data = new LabeledSecretContextToSourceDataProvider().get();
SourceData sourceData = data.apply(context);
Assertions.assertEquals(sourceData.sourceData().size(), 2);
Assertions.assertEquals(sourceData.sourceData().get("color-secret.color-secret-k8s.one"), "1");
Assertions.assertEquals(sourceData.sourceData().get("color-secret.color-secret-k8s.two"), "2");
Assertions.assertEquals(sourceData.sourceName(), "secret.color-secret.color-secret-k8s.default");
}
/**
* <pre>
* - secret "color-secret" with label "{color:blue}"
* - secret "shape-secret" with labels "{color:blue, shape:round}"
* - secret "no-fit" with labels "{tag:no-fit}"
* - secret "color-secret-k8s" with label "{color:red}"
* - secret "shape-secret-k8s" with label "{shape:triangle}"
* </pre>
*/
@Test
void searchWithLabelsTwoSecretsFoundAndOneFromProfileFound() {
V1Secret colorSecret = new V1SecretBuilder().withMetadata(new V1ObjectMetaBuilder()
.withLabels(Map.of("color", "blue")).withNamespace(NAMESPACE).withName("color-secret").build())
.addToData("one", "1".getBytes()).build();
V1Secret shapeSecret = new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withLabels(Map.of("color", "blue", "shape", "round"))
.withNamespace(NAMESPACE).withName("shape-secret").build())
.addToData("two", "2".getBytes()).build();
V1Secret noFit = new V1SecretBuilder().withMetadata(new V1ObjectMetaBuilder()
.withLabels(Map.of("tag", "no-fit")).withNamespace(NAMESPACE).withName("no-fit").build())
.addToData("three", "3".getBytes()).build();
V1Secret colorSecretK8s = new V1SecretBuilder().withMetadata(new V1ObjectMetaBuilder()
.withLabels(Map.of("color", "red")).withNamespace(NAMESPACE).withName("color-secret-k8s").build())
.addToData("four", "4".getBytes()).build();
V1Secret shapeSecretK8s = new V1SecretBuilder().withMetadata(new V1ObjectMetaBuilder()
.withLabels(Map.of("shape", "triangle")).withNamespace(NAMESPACE).withName("shape-secret-k8s").build())
.addToData("five", "5".getBytes()).build();
V1SecretList secretList = new V1SecretList().addItemsItem(colorSecret).addItemsItem(shapeSecret)
.addItemsItem(noFit).addItemsItem(colorSecretK8s).addItemsItem(shapeSecretK8s);
stubCall(secretList);
CoreV1Api api = new CoreV1Api();
MockEnvironment environment = new MockEnvironment();
environment.setActiveProfiles("k8s");
NormalizedSource source = new LabeledSecretNormalizedSource(NAMESPACE, Map.of("color", "blue"), false,
ConfigUtils.Prefix.DELAYED, true);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, environment);
KubernetesClientContextToSourceData data = new LabeledSecretContextToSourceDataProvider().get();
SourceData sourceData = data.apply(context);
Assertions.assertEquals(sourceData.sourceData().size(), 4);
Assertions.assertEquals(
sourceData.sourceData().get("color-secret.color-secret-k8s.shape-secret.shape-secret-k8s.one"), "1");
Assertions.assertEquals(
sourceData.sourceData().get("color-secret.color-secret-k8s.shape-secret.shape-secret-k8s.two"), "2");
Assertions.assertEquals(
sourceData.sourceData().get("color-secret.color-secret-k8s.shape-secret.shape-secret-k8s.four"), "4");
Assertions.assertEquals(
sourceData.sourceData().get("color-secret.color-secret-k8s.shape-secret.shape-secret-k8s.five"), "5");
Assertions.assertEquals(sourceData.sourceName(),
"secret.color-secret.color-secret-k8s.shape-secret.shape-secret-k8s.default");
}
/**
* yaml/properties gets special treatment
*/
@Test
void testYaml() {
V1Secret colorSecret = new V1SecretBuilder().withMetadata(new V1ObjectMetaBuilder()
.withLabels(Map.of("color", "blue")).withNamespace(NAMESPACE).withName("color-secret").build())
.addToData("test.yaml", "color: blue".getBytes()).build();
V1SecretList secretList = new V1SecretList().addItemsItem(colorSecret);
stubCall(secretList);
CoreV1Api api = new CoreV1Api();
NormalizedSource source = new LabeledSecretNormalizedSource(NAMESPACE, Map.of("color", "blue"), false,
ConfigUtils.Prefix.DEFAULT, true);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());
KubernetesClientContextToSourceData data = new LabeledSecretContextToSourceDataProvider().get();
SourceData sourceData = data.apply(context);
Assertions.assertEquals(sourceData.sourceData().size(), 1);
Assertions.assertEquals(sourceData.sourceData().get("color"), "blue");
Assertions.assertEquals(sourceData.sourceName(), "secret.color-secret.default");
}
private void stubCall(V1SecretList list) {
stubFor(get("/api/v1/namespaces/default/secrets")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(list))));
}
}

View File

@@ -25,6 +25,7 @@ import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.Configuration;
import io.kubernetes.client.openapi.JSON;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import io.kubernetes.client.openapi.models.V1ConfigMap;
import io.kubernetes.client.openapi.models.V1ConfigMapBuilder;
import io.kubernetes.client.openapi.models.V1ConfigMapList;
import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder;
@@ -34,12 +35,10 @@ import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.cloud.kubernetes.commons.config.ConfigMapPropertySource;
import org.springframework.cloud.kubernetes.commons.config.ConfigUtils;
import org.springframework.cloud.kubernetes.commons.config.NamedConfigMapNormalizedSource;
import org.springframework.cloud.kubernetes.commons.config.NormalizedSource;
import org.springframework.cloud.kubernetes.commons.config.SourceData;
import org.springframework.core.env.Environment;
import org.springframework.mock.env.MockEnvironment;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
@@ -56,8 +55,14 @@ class NamedConfigMapContextToSourceDataProviderTests {
private static final String RED_CONFIG_MAP_NAME = "red";
private static final String RED_WITH_PROFILE_CONFIG_MAP_NAME = RED_CONFIG_MAP_NAME + "-with-profile";
private static final String BLUE_CONFIG_MAP_NAME = "blue";
private static final Map<String, String> COLOR_REALLY_RED = Map.of("color", "really-red");
private static final Map<String, String> TASTE_MANGO = Map.of("taste", "mango");
@BeforeAll
static void setup() {
WireMockServer wireMockServer = new WireMockServer(options().dynamicPort());
@@ -76,92 +81,90 @@ class NamedConfigMapContextToSourceDataProviderTests {
}
/**
* we have a single config map deployed. it does not match our query.
* <pre>
* one configmap deployed with name "red"
* we search by name, but for the "blue" one, as such not find it
* </pre>
*/
@Test
void noMatch() {
V1ConfigMap redConfigMap = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME).withNamespace(NAMESPACE).build())
.addToData(COLOR_REALLY_RED).build();
V1ConfigMapList configMapList = new V1ConfigMapList()
.addItemsItem(
new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME)
.withNamespace(NAMESPACE).withResourceVersion("1").build())
.addToData("color", "really-red").build());
V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(redConfigMap);
stubCall(configMapList);
CoreV1Api api = new CoreV1Api();
stubFor(get("/api/v1/namespaces/default/configmaps")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(configMapList))));
NormalizedSource source = new NamedConfigMapNormalizedSource(BLUE_CONFIG_MAP_NAME, NAMESPACE, true, false);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());
KubernetesClientContextToSourceData data = NamedConfigMapContextToSourceDataProvider.of(Dummy::processEntries)
.get();
KubernetesClientContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get();
SourceData sourceData = data.apply(context);
Assertions.assertEquals(sourceData.sourceName(), "configmap.blue.default");
Assertions.assertEquals(sourceData.sourceData(), Collections.emptyMap());
Assertions.assertEquals(sourceData.sourceData(), Map.of());
}
/**
* we have a single config map deployed. it matches our query.
* <pre>
* one configmap deployed with name "red"
* we search by name, for the "red" one, as such we find it
* </pre>
*/
@Test
void match() {
V1ConfigMapList configMapList = new V1ConfigMapList()
.addItemsItem(
new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME)
.withNamespace(NAMESPACE).withResourceVersion("1").build())
.addToData("color", "really-red").build());
V1ConfigMap configMap = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME).withNamespace(NAMESPACE).build())
.addToData(COLOR_REALLY_RED).build();
V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(configMap);
stubCall(configMapList);
CoreV1Api api = new CoreV1Api();
stubFor(get("/api/v1/namespaces/default/configmaps")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(configMapList))));
NormalizedSource source = new NamedConfigMapNormalizedSource(RED_CONFIG_MAP_NAME, NAMESPACE, true, false);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());
KubernetesClientContextToSourceData data = NamedConfigMapContextToSourceDataProvider.of(Dummy::processEntries)
.get();
KubernetesClientContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get();
SourceData sourceData = data.apply(context);
Assertions.assertEquals(sourceData.sourceName(), "configmap.red.default");
Assertions.assertEquals(sourceData.sourceData(), Collections.singletonMap("color", "really-red"));
Assertions.assertEquals(sourceData.sourceData(), COLOR_REALLY_RED);
}
/**
* we have two config maps deployed. one matches the query name. the other matches the
* active profile + name, thus is taken also.
* <pre>
* - two configmaps deployed : "red" and "red-with-profile".
* - "red" is matched directly, "red-with-profile" is matched because we have an active profile
* "active-profile"
* </pre>
*/
@Test
void matchIncludeSingleProfile() {
V1ConfigMapList configMapList = new V1ConfigMapList()
.addItemsItem(
new V1ConfigMapBuilder()
.withMetadata(
new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME).withNamespace(NAMESPACE)
.withResourceVersion("1").build())
.addToData("color", "really-red").build())
.addItemsItem(new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME + "-with-profile")
.withNamespace(NAMESPACE).withResourceVersion("1").build())
.addToData("taste", "mango").build());
V1ConfigMap red = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME).withNamespace(NAMESPACE).build())
.addToData(COLOR_REALLY_RED).build();
V1ConfigMap redWithProfile = new V1ConfigMapBuilder().withMetadata(
new V1ObjectMetaBuilder().withName(RED_WITH_PROFILE_CONFIG_MAP_NAME).withNamespace(NAMESPACE).build())
.addToData(TASTE_MANGO).build();
V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(red).addItemsItem(redWithProfile);
stubCall(configMapList);
CoreV1Api api = new CoreV1Api();
stubFor(get("/api/v1/namespaces/default/configmaps")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(configMapList))));
NormalizedSource source = new NamedConfigMapNormalizedSource(RED_CONFIG_MAP_NAME, NAMESPACE, true, true);
MockEnvironment environment = new MockEnvironment();
environment.setActiveProfiles("with-profile");
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, environment);
KubernetesClientContextToSourceData data = NamedConfigMapContextToSourceDataProvider.of(Dummy::processEntries)
.get();
KubernetesClientContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get();
SourceData sourceData = data.apply(context);
Assertions.assertEquals(sourceData.sourceName(), "configmap.red.red-with-profile.default");
@@ -172,29 +175,29 @@ class NamedConfigMapContextToSourceDataProviderTests {
}
/**
* we have two config maps deployed. one matches the query name. the other matches the
* active profile + name, thus is taken also. This takes into consideration the
* prefix, that we explicitly specify. Notice that prefix works for profile based
* config maps as well.
* <pre>
* - two configmaps deployed : "red" and "red-with-profile".
* - "red" is matched directly, "red-with-profile" is matched because we have an active profile
* "active-profile"
* - This takes into consideration the prefix, that we explicitly specify.
* Notice that prefix works for profile based config maps as well.
* </pre>
*/
@Test
void matchIncludeSingleProfileWithPrefix() {
V1ConfigMapList configMapList = new V1ConfigMapList()
.addItemsItem(
new V1ConfigMapBuilder()
.withMetadata(
new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME).withNamespace(NAMESPACE)
.withResourceVersion("1").build())
.addToData("color", "really-red").build())
.addItemsItem(new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME + "-with-profile")
.withNamespace(NAMESPACE).withResourceVersion("1").build())
.addToData("taste", "mango").build());
V1ConfigMap red = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME).withNamespace(NAMESPACE).build())
.addToData(COLOR_REALLY_RED).build();
V1ConfigMap redWithTaste = new V1ConfigMapBuilder().withMetadata(
new V1ObjectMetaBuilder().withName(RED_WITH_PROFILE_CONFIG_MAP_NAME).withNamespace(NAMESPACE).build())
.addToData(TASTE_MANGO).build();
V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(red).addItemsItem(redWithTaste);
stubCall(configMapList);
CoreV1Api api = new CoreV1Api();
stubFor(get("/api/v1/namespaces/default/configmaps")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(configMapList))));
ConfigUtils.Prefix prefix = ConfigUtils.findPrefix("some", false, false, null);
NormalizedSource source = new NamedConfigMapNormalizedSource(RED_CONFIG_MAP_NAME, NAMESPACE, true, prefix,
true);
@@ -202,8 +205,7 @@ class NamedConfigMapContextToSourceDataProviderTests {
environment.setActiveProfiles("with-profile");
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, environment);
KubernetesClientContextToSourceData data = NamedConfigMapContextToSourceDataProvider.of(Dummy::processEntries)
.get();
KubernetesClientContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get();
SourceData sourceData = data.apply(context);
Assertions.assertEquals(sourceData.sourceName(), "configmap.red.red-with-profile.default");
@@ -214,35 +216,35 @@ class NamedConfigMapContextToSourceDataProviderTests {
}
/**
* we have three config maps deployed. one matches the query name. the other two match
* the active profile + name, thus are taken also. This takes into consideration the
* prefix, that we explicitly specify. Notice that prefix works for profile based
* config maps as well.
* <pre>
* - three configmaps deployed : "red", "red-with-taste" and "red-with-shape"
* - "red" is matched directly, the other two are matched because of active profiles
* - This takes into consideration the prefix, that we explicitly specify.
* Notice that prefix works for profile based config maps as well.
* </pre>
*/
@Test
void matchIncludeTwoProfilesWithPrefix() {
V1ConfigMapList configMapList = new V1ConfigMapList()
.addItemsItem(
new V1ConfigMapBuilder()
.withMetadata(
new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME).withNamespace(NAMESPACE)
.withResourceVersion("1").build())
.addToData("color", "really-red").build())
.addItemsItem(
new V1ConfigMapBuilder()
.withMetadata(
new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME + "-with-taste")
.withNamespace(NAMESPACE).withResourceVersion("1").build())
.addToData("taste", "mango").build())
.addItemsItem(new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME + "-with-shape")
.withNamespace(NAMESPACE).withResourceVersion("1").build())
.addToData("shape", "round").build());
V1ConfigMap red = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME).withNamespace(NAMESPACE).build())
.addToData(COLOR_REALLY_RED).build();
V1ConfigMap redWithTaste = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME + "-with-taste")
.withNamespace(NAMESPACE).withResourceVersion("1").build())
.addToData(TASTE_MANGO).build();
V1ConfigMap redWithShape = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder()
.withName(RED_CONFIG_MAP_NAME + "-with-shape").withNamespace(NAMESPACE).build())
.addToData("shape", "round").build();
V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(red).addItemsItem(redWithTaste)
.addItemsItem(redWithShape);
stubCall(configMapList);
CoreV1Api api = new CoreV1Api();
stubFor(get("/api/v1/namespaces/default/configmaps")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(configMapList))));
ConfigUtils.Prefix prefix = ConfigUtils.findPrefix("some", false, false, null);
NormalizedSource source = new NamedConfigMapNormalizedSource(RED_CONFIG_MAP_NAME, NAMESPACE, true, prefix,
true);
@@ -250,11 +252,10 @@ class NamedConfigMapContextToSourceDataProviderTests {
environment.setActiveProfiles("with-taste", "with-shape");
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, environment);
KubernetesClientContextToSourceData data = NamedConfigMapContextToSourceDataProvider.of(Dummy::processEntries)
.get();
KubernetesClientContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get();
SourceData sourceData = data.apply(context);
Assertions.assertEquals(sourceData.sourceName(), "configmap.red.red-with-taste.red-with-shape.default");
Assertions.assertEquals(sourceData.sourceName(), "configmap.red.red-with-shape.red-with-taste.default");
Assertions.assertEquals(sourceData.sourceData().size(), 3);
Assertions.assertEquals(sourceData.sourceData().get("some.color"), "really-red");
Assertions.assertEquals(sourceData.sourceData().get("some.taste"), "mango");
@@ -262,25 +263,29 @@ class NamedConfigMapContextToSourceDataProviderTests {
}
// when reading config maps and creating normalized sources, we will always be
// providing a name
// for the config map; even if one is not provided explicitly.
/**
* <pre>
* proves that an implicit configmap is going to be generated and read, even if
* we did not provide one
* </pre>
*/
@Test
void matchWithName() {
V1ConfigMapList configMapList = new V1ConfigMapList()
.addItemsItem(new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder().withName("application")
.withNamespace(NAMESPACE).withResourceVersion("1").build()).addToData("color", "red").build());
V1ConfigMap red = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("application").withNamespace(NAMESPACE).build())
.addToData("color", "red").build();
V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(red);
stubCall(configMapList);
CoreV1Api api = new CoreV1Api();
stubFor(get("/api/v1/namespaces/default/configmaps")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(configMapList))));
ConfigUtils.Prefix prefix = ConfigUtils.findPrefix("some", false, false, null);
NormalizedSource source = new NamedConfigMapNormalizedSource("application", NAMESPACE, true, prefix, false);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());
KubernetesClientContextToSourceData data = NamedConfigMapContextToSourceDataProvider.of(Dummy::processEntries)
.get();
KubernetesClientContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get();
SourceData sourceData = data.apply(context);
Assertions.assertEquals(sourceData.sourceName(), "configmap.application.default");
@@ -288,48 +293,95 @@ class NamedConfigMapContextToSourceDataProviderTests {
}
/**
* NamedSecretContextToSourceDataProvider gets as input a
* KubernetesClientConfigContext. This context has a namespace as well as a
* NormalizedSource, that has a namespace too. It is easy to get confused in code on
* which namespace to use. This test makes sure that we use the proper one.
* <pre>
* - NamedSecretContextToSourceDataProvider gets as input a KubernetesClientConfigContext.
* - This context has a namespace as well as a NormalizedSource, that has a namespace too.
* - This test makes sure that we use the proper one.
* </pre>
*/
@Test
void namespaceMatch() {
V1ConfigMapList configMapList = new V1ConfigMapList()
.addItemsItem(
new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME)
.withNamespace(NAMESPACE).withResourceVersion("1").build())
.addToData("color", "really-red").build());
V1ConfigMap configMap = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME).withNamespace(NAMESPACE).build())
.addToData(COLOR_REALLY_RED).build();
V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(configMap);
stubCall(configMapList);
CoreV1Api api = new CoreV1Api();
stubFor(get("/api/v1/namespaces/default/configmaps")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(configMapList))));
NormalizedSource source = new NamedConfigMapNormalizedSource(RED_CONFIG_MAP_NAME, NAMESPACE + "nope", true,
false);
String wrongNamespace = NAMESPACE + "nope";
NormalizedSource source = new NamedConfigMapNormalizedSource(RED_CONFIG_MAP_NAME, wrongNamespace, true, false);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());
KubernetesClientContextToSourceData data = NamedConfigMapContextToSourceDataProvider.of(Dummy::processEntries)
.get();
KubernetesClientContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get();
SourceData sourceData = data.apply(context);
Assertions.assertEquals(sourceData.sourceName(), "configmap.red.default");
Assertions.assertEquals(sourceData.sourceData(), Collections.singletonMap("color", "really-red"));
Assertions.assertEquals(sourceData.sourceData(), COLOR_REALLY_RED);
}
// needed only to allow access to the super methods
private static final class Dummy extends ConfigMapPropertySource {
/**
* <pre>
* - proves that single yaml file gets special treatment
* </pre>
*/
@Test
void testSingleYaml() {
V1ConfigMap singleYaml = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME).withNamespace(NAMESPACE).build())
.addToData("single.yaml", "key: value").build();
V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(singleYaml);
private Dummy() {
super(SourceData.emptyRecord("dummy-name"));
}
stubCall(configMapList);
CoreV1Api api = new CoreV1Api();
private static Map<String, Object> processEntries(Map<String, String> map, Environment environment) {
return processAllEntries(map, environment);
}
NormalizedSource source = new NamedConfigMapNormalizedSource(RED_CONFIG_MAP_NAME, NAMESPACE, true, false);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());
KubernetesClientContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get();
SourceData sourceData = data.apply(context);
Assertions.assertEquals(sourceData.sourceName(), "configmap.red.default");
Assertions.assertEquals(sourceData.sourceData(), Map.of("key", "value"));
}
/**
* <pre>
* - one configmap is deployed with name "one"
* - profile is enabled with name "k8s"
*
* we assert that the name of the source is "one" and does not contain "one-dev"
* </pre>
*/
@Test
void testCorrectNameWithProfile() {
V1ConfigMap one = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("one").withNamespace(NAMESPACE).build())
.addToData("key", "value").build();
V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(one);
stubCall(configMapList);
CoreV1Api api = new CoreV1Api();
MockEnvironment environment = new MockEnvironment();
environment.setActiveProfiles("k8s");
NormalizedSource source = new NamedConfigMapNormalizedSource("one", NAMESPACE, true, true);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, environment);
KubernetesClientContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get();
SourceData sourceData = data.apply(context);
Assertions.assertEquals(sourceData.sourceName(), "configmap.one.default");
Assertions.assertEquals(sourceData.sourceData(), Map.of("key", "value"));
}
private void stubCall(V1ConfigMapList list) {
stubFor(get("/api/v1/namespaces/default/configmaps")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(list))));
}
}

View File

@@ -26,14 +26,17 @@ import io.kubernetes.client.openapi.Configuration;
import io.kubernetes.client.openapi.JSON;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder;
import io.kubernetes.client.openapi.models.V1Secret;
import io.kubernetes.client.openapi.models.V1SecretBuilder;
import io.kubernetes.client.openapi.models.V1SecretList;
import io.kubernetes.client.openapi.models.V1SecretListBuilder;
import io.kubernetes.client.util.ClientBuilder;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.cloud.kubernetes.commons.config.ConfigUtils;
import org.springframework.cloud.kubernetes.commons.config.NamedSecretNormalizedSource;
import org.springframework.cloud.kubernetes.commons.config.NormalizedSource;
import org.springframework.cloud.kubernetes.commons.config.SourceData;
@@ -46,8 +49,12 @@ import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options
class NamedSecretContextToSourceDataProviderTests {
private static final ConfigUtils.Prefix PREFIX = ConfigUtils.findPrefix("some", false, false, "irrelevant");
private static final String NAMESPACE = "default";
private static final Map<String, byte[]> COLOR_REALLY_RED = Map.of("color", "really-red".getBytes());
@BeforeAll
static void setup() {
WireMockServer wireMockServer = new WireMockServer(options().dynamicPort());
@@ -66,25 +73,20 @@ class NamedSecretContextToSourceDataProviderTests {
}
/**
*
* /** we have a single secret deployed. it matched the name in our queries
* we have a single secret deployed. it matched the name in our queries
*/
@Test
void singleSecretMatchAgainstLabels() {
V1SecretList secretList = new V1SecretList()
.addItemsItem(
new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("red")
.withResourceVersion("1").build())
.addToData("color", "really-red".getBytes()).build());
V1Secret red = new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("red").build())
.addToData(COLOR_REALLY_RED).build();
V1SecretList secretList = new V1SecretList().addItemsItem(red);
stubCall(secretList);
CoreV1Api api = new CoreV1Api();
stubFor(get("/api/v1/namespaces/default/secrets")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(secretList))));
// blue does not match red
NormalizedSource source = new NamedSecretNormalizedSource("red", NAMESPACE, false);
NormalizedSource source = new NamedSecretNormalizedSource("red", NAMESPACE, false, false);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());
@@ -103,29 +105,25 @@ class NamedSecretContextToSourceDataProviderTests {
@Test
void twoSecretMatchAgainstLabels() {
V1SecretList secretList = new V1SecretList()
.addItemsItem(
new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("red")
.withResourceVersion("1").build())
.addToData("color", "really-red".getBytes()).build())
.addItemsItem(
new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("blue")
.withResourceVersion("1").build())
.addToData("color", "really-red".getBytes()).build())
.addItemsItem(
new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("pink")
.withResourceVersion("1").build())
.addToData("color", "really-red".getBytes()).build());
V1Secret red = new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("red").build())
.addToData(COLOR_REALLY_RED).build();
V1Secret blue = new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("blue").build())
.addToData(COLOR_REALLY_RED).build();
V1Secret pink = new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("pink").build())
.addToData(COLOR_REALLY_RED).build();
V1SecretList secretList = new V1SecretListBuilder().addToItems(red).addToItems(blue).addToItems(pink).build();
stubCall(secretList);
CoreV1Api api = new CoreV1Api();
stubFor(get("/api/v1/namespaces/default/secrets")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(secretList))));
// blue does not match red
NormalizedSource source = new NamedSecretNormalizedSource("red", NAMESPACE, false);
// blue does not match red, nor pink
NormalizedSource source = new NamedSecretNormalizedSource("red", NAMESPACE, false, false);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());
@@ -144,19 +142,16 @@ class NamedSecretContextToSourceDataProviderTests {
@Test
void testSecretNoMatch() {
V1SecretList secretList = new V1SecretList()
.addItemsItem(
new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("red")
.withResourceVersion("1").build())
.addToData("color", "really-red".getBytes()).build());
V1Secret secret = new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("red").build())
.addToData(COLOR_REALLY_RED).build();
V1SecretList secretList = new V1SecretList().addItemsItem(secret);
stubCall(secretList);
CoreV1Api api = new CoreV1Api();
stubFor(get("/api/v1/namespaces/default/secrets")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(secretList))));
// blue does not match red
NormalizedSource source = new NamedSecretNormalizedSource("blue", NAMESPACE, false);
NormalizedSource source = new NamedSecretNormalizedSource("blue", NAMESPACE, false, false);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());
@@ -167,22 +162,26 @@ class NamedSecretContextToSourceDataProviderTests {
Assertions.assertEquals(sourceData.sourceData(), Collections.emptyMap());
}
/**
* <pre>
* - LabeledSecretContextToSourceDataProvider gets as input a KubernetesClientConfigContext.
* - This context has a namespace as well as a NormalizedSource, that has a namespace too.
* - This test makes sure that we use the proper one.
* </pre>
*/
@Test
void namespaceMatch() {
V1SecretList secretList = new V1SecretList()
.addItemsItem(
new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("red")
.withResourceVersion("1").build())
.addToData("color", "really-red".getBytes()).build());
V1Secret secret = new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("red").build())
.addToData(COLOR_REALLY_RED).build();
V1SecretList secretList = new V1SecretList().addItemsItem(secret);
stubCall(secretList);
CoreV1Api api = new CoreV1Api();
stubFor(get("/api/v1/namespaces/default/secrets")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(secretList))));
// blue does not match red
NormalizedSource source = new NamedSecretNormalizedSource("red", NAMESPACE + "nope", false);
String wrongNamespace = NAMESPACE + "nope";
NormalizedSource source = new NamedSecretNormalizedSource("red", wrongNamespace, false, false);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());
@@ -193,4 +192,150 @@ class NamedSecretContextToSourceDataProviderTests {
Assertions.assertEquals(sourceData.sourceData(), Map.of("color", "really-red"));
}
/**
* we have two secrets deployed. one matches the query name. the other matches the
* active profile + name, thus is taken also.
*/
@Test
void matchIncludeSingleProfile() {
V1Secret red = new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("red").build())
.addToData(COLOR_REALLY_RED).build();
V1Secret mango = new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("red-with-profile").build())
.addToData("taste", "mango".getBytes()).build();
V1SecretList secretList = new V1SecretList().addItemsItem(red).addItemsItem(mango);
stubCall(secretList);
CoreV1Api api = new CoreV1Api();
NormalizedSource source = new NamedSecretNormalizedSource("red", NAMESPACE, false, true);
MockEnvironment environment = new MockEnvironment();
environment.addActiveProfile("with-profile");
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, environment);
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.sourceData().get("taste"), "mango");
}
/**
* we have two secrets deployed. one matches the query name. the other matches the
* active profile + name, thus is taken also. This takes into consideration the
* prefix, that we explicitly specify. Notice that prefix works for profile based
* secrets as well.
*/
@Test
void matchIncludeSingleProfileWithPrefix() {
V1Secret red = new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("red").build())
.addToData(COLOR_REALLY_RED).build();
V1Secret mango = new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("red-with-taste").build())
.addToData("taste", "mango".getBytes()).build();
V1SecretList secretList = new V1SecretList().addItemsItem(red).addItemsItem(mango);
stubCall(secretList);
CoreV1Api api = new CoreV1Api();
NormalizedSource source = new NamedSecretNormalizedSource("red", NAMESPACE, true, PREFIX, true);
MockEnvironment environment = new MockEnvironment();
environment.addActiveProfile("with-taste");
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, environment);
KubernetesClientContextToSourceData data = new NamedSecretContextToSourceDataProvider().get();
SourceData sourceData = data.apply(context);
Assertions.assertEquals(sourceData.sourceName(), "secret.red.red-with-taste.default");
Assertions.assertEquals(sourceData.sourceData().size(), 2);
Assertions.assertEquals(sourceData.sourceData().get("some.color"), "really-red");
Assertions.assertEquals(sourceData.sourceData().get("some.taste"), "mango");
}
/**
* we have three secrets deployed. one matches the query name. the other two match the
* active profile + name, thus are taken also. This takes into consideration the
* prefix, that we explicitly specify. Notice that prefix works for profile based
* config maps as well.
*/
@Test
void matchIncludeTwoProfilesWithPrefix() {
V1Secret red = new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("red").build())
.addToData(COLOR_REALLY_RED).build();
V1Secret mango = new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("red-with-taste").build())
.addToData("taste", "mango".getBytes()).build();
V1Secret shape = new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("red-with-shape").build())
.addToData("shape", "round".getBytes()).build();
V1SecretList secretList = new V1SecretList().addItemsItem(red).addItemsItem(mango).addItemsItem(shape);
stubCall(secretList);
CoreV1Api api = new CoreV1Api();
NormalizedSource source = new NamedSecretNormalizedSource("red", NAMESPACE, true, PREFIX, true);
MockEnvironment environment = new MockEnvironment();
environment.setActiveProfiles("with-taste", "with-shape");
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, environment);
KubernetesClientContextToSourceData data = new NamedSecretContextToSourceDataProvider().get();
SourceData sourceData = data.apply(context);
Assertions.assertEquals(sourceData.sourceName(), "secret.red.red-with-shape.red-with-taste.default");
Assertions.assertEquals(sourceData.sourceData().size(), 3);
Assertions.assertEquals(sourceData.sourceData().get("some.color"), "really-red");
Assertions.assertEquals(sourceData.sourceData().get("some.taste"), "mango");
Assertions.assertEquals(sourceData.sourceData().get("some.shape"), "round");
}
/**
* <pre>
* - proves that single yaml file gets special treatment
* </pre>
*/
@Test
void testSingleYaml() {
V1Secret singleYaml = new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("single-yaml").withNamespace(NAMESPACE).build())
.addToData("single.yaml", "key: value".getBytes()).build();
V1SecretList secretList = new V1SecretList().addItemsItem(singleYaml);
stubCall(secretList);
CoreV1Api api = new CoreV1Api();
NormalizedSource source = new NamedSecretNormalizedSource("single-yaml", NAMESPACE, true, false);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());
KubernetesClientContextToSourceData data = new NamedSecretContextToSourceDataProvider().get();
SourceData sourceData = data.apply(context);
Assertions.assertEquals(sourceData.sourceName(), "secret.single-yaml.default");
Assertions.assertEquals(sourceData.sourceData(), Map.of("key", "value"));
}
private void stubCall(V1SecretList list) {
stubFor(get("/api/v1/namespaces/default/secrets")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(list))));
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix.properties.Four;
import org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix.properties.One;
import org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix.properties.Three;
import org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix.properties.Two;
@SpringBootApplication
@EnableConfigurationProperties({ One.class, Two.class, Three.class, Four.class })
public class LabeledConfigMapWithPrefixApp {
public static void main(String[] args) {
SpringApplication.run(LabeledConfigMapWithPrefixApp.class, args);
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @author wind57
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = LabeledConfigMapWithPrefixApp.class,
properties = { "spring.cloud.bootstrap.name=labeled-configmap-with-prefix",
"labeled.config.map.with.prefix.stub=true", "spring.main.cloud-platform=KUBERNETES",
"spring.cloud.bootstrap.enabled=true", "spring.cloud.kubernetes.client.namespace=spring-k8s" })
class LabeledConfigMapWithPrefixBootstrapTests extends LabeledConfigMapWithPrefixTests {
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import io.kubernetes.client.util.ClientBuilder;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.mockito.MockedStatic;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.kubernetes.client.KubernetesClientUtils;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
import static org.mockito.Mockito.mockStatic;
import static org.springframework.cloud.kubernetes.client.config.boostrap.stubs.LabeledConfigMapWithPrefixConfigurationStub.stubData;
/**
* @author wind57
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = LabeledConfigMapWithPrefixApp.class,
properties = { "spring.cloud.application.name=labeled-configmap-with-prefix",
"labeled.config.map.with.prefix.stub=true", "spring.main.cloud-platform=KUBERNETES",
"spring.config.import=kubernetes:,classpath:./labeled-configmap-with-prefix.yaml",
"spring.cloud.kubernetes.client.namespace=spring-k8s" })
class LabeledConfigMapWithPrefixConfigDataTests extends LabeledConfigMapWithPrefixTests {
private static MockedStatic<KubernetesClientUtils> clientUtilsMock;
@BeforeAll
static void wireMock() {
WireMockServer server = new WireMockServer(options().dynamicPort());
server.start();
WireMock.configureFor("localhost", server.port());
clientUtilsMock = mockStatic(KubernetesClientUtils.class);
clientUtilsMock.when(KubernetesClientUtils::kubernetesApiClient)
.thenReturn(new ClientBuilder().setBasePath(server.baseUrl()).build());
stubData();
}
@AfterAll
static void teardown() {
clientUtilsMock.close();
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix;
import com.github.tomakehurst.wiremock.client.WireMock;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.reactive.server.WebTestClient;
/**
* Stud data is in
* {@link org.springframework.cloud.kubernetes.client.config.boostrap.stubs.LabeledConfigMapWithPrefixConfigurationStub}
*
* @author wind57
*/
abstract class LabeledConfigMapWithPrefixTests {
@Autowired
private WebTestClient webClient;
@AfterEach
void afterEach() {
WireMock.reset();
}
@AfterAll
static void afterAll() {
WireMock.shutdownServer();
}
/**
* <pre>
* 'spring.cloud.kubernetes.configmap.useNameAsPrefix=true'
* 'spring.cloud.kubernetes.configmap.sources[0].useNameAsPrefix=false'
* ("one.property", "one")
*
* As such: @ConfigurationProperties("one")
* </pre>
*/
@Test
void testOne() {
this.webClient.get().uri("/labeled-configmap/prefix/one").exchange().expectStatus().isOk()
.expectBody(String.class).value(Matchers.equalTo("one"));
}
/**
* <pre>
* 'spring.cloud.kubernetes.config.useNameAsPrefix=true'
* 'spring.cloud.kubernetes.config.sources[1].explicitPrefix=two'
* ("property", "two")
*
* As such: @ConfigurationProperties("two")
* </pre>
*/
@Test
void testTwo() {
this.webClient.get().uri("/labeled-configmap/prefix/two").exchange().expectStatus().isOk()
.expectBody(String.class).value(Matchers.equalTo("two"));
}
/**
* <pre>
* 'spring.cloud.kubernetes.config.useNameAsPrefix=true'
* 'spring.cloud.kubernetes.config.sources[2].labels=letter:c'
* ("property", "three")
*
* We find the configmap by labels, and use it's name as the prefix.
*
* As such: @ConfigurationProperties(prefix = "configmap-three")
* </pre>
*/
@Test
void testThree() {
this.webClient.get().uri("/labeled-configmap/prefix/three").exchange().expectStatus().isOk()
.expectBody(String.class).value(Matchers.equalTo("three"));
}
/**
* <pre>
* 'spring.cloud.kubernetes.config.useNameAsPrefix=true'
* 'spring.cloud.kubernetes.config.sources[3].labels=letter:d'
* ("property", "four")
*
* We find the configmap by labels, and use it's name as the prefix.
*
* As such: @ConfigurationProperties(prefix = "configmap-four")
* </pre>
*/
@Test
void testFour() {
this.webClient.get().uri("/labeled-configmap/prefix/four").exchange().expectStatus().isOk()
.expectBody(String.class).value(Matchers.equalTo("four"));
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix.controller;
import org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix.properties.Four;
import org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix.properties.One;
import org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix.properties.Three;
import org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix.properties.Two;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LabeledConfigMapWithPrefixController {
private final One one;
private final Two two;
private final Three three;
private final Four four;
public LabeledConfigMapWithPrefixController(One one, Two two, Three three, Four four) {
this.one = one;
this.two = two;
this.three = three;
this.four = four;
}
@GetMapping("/labeled-configmap/prefix/one")
public String one() {
return one.getProperty();
}
@GetMapping("/labeled-configmap/prefix/two")
public String two() {
return two.getProperty();
}
@GetMapping("/labeled-configmap/prefix/three")
public String three() {
return three.getProperty();
}
@GetMapping("/labeled-configmap/prefix/four")
public String four() {
return four.getProperty();
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "configmap-four")
public class Four {
private String property;
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.include_profile_specific_sources.properties;
package org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "configmap-three")
public class Three {
private String property;
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.include_profile_specific_sources.properties;
package org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_profile;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_profile.properties.Blue;
import org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_profile.properties.Green;
/**
* @author wind57
*/
@SpringBootApplication
@EnableConfigurationProperties({ Blue.class, Green.class })
public class LabeledConfigMapWithProfileApp {
public static void main(String[] args) {
SpringApplication.run(LabeledConfigMapWithProfileApp.class, args);
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_profile;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
/**
* @author wind57
*/
@ActiveProfiles({ "k8s", "prod" })
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = LabeledConfigMapWithProfileApp.class,
properties = { "spring.cloud.bootstrap.name=labeled-configmap-with-profile",
"labeled.config.map.with.profile.stub=true", "spring.main.cloud-platform=KUBERNETES",
"spring.cloud.bootstrap.enabled=true", "spring.cloud.kubernetes.client.namespace=spring-k8s" })
class LabeledConfigMapWithProfileBootstrapTests extends LabeledConfigMapWithProfileTests {
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_profile;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import io.kubernetes.client.util.ClientBuilder;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.mockito.MockedStatic;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.kubernetes.client.KubernetesClientUtils;
import org.springframework.test.context.ActiveProfiles;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
import static org.mockito.Mockito.mockStatic;
import static org.springframework.cloud.kubernetes.client.config.boostrap.stubs.LabeledConfigMapWithProfileConfigurationStub.stubData;
/**
* @author wind57
*/
@ActiveProfiles({ "k8s", "prod" })
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = LabeledConfigMapWithProfileApp.class,
properties = { "spring.cloud.application.name=labeled-configmap-with-profile",
"labeled.config.map.with.profile.stub=true", "spring.main.cloud-platform=KUBERNETES",
"spring.config.import=kubernetes:,classpath:./labeled-configmap-with-profile.yaml",
"spring.cloud.kubernetes.client.namespace=spring-k8s" })
class LabeledConfigMapWithProfileConfigDataTests extends LabeledConfigMapWithProfileTests {
private static MockedStatic<KubernetesClientUtils> clientUtilsMock;
@BeforeAll
static void wireMock() {
WireMockServer server = new WireMockServer(options().dynamicPort());
server.start();
WireMock.configureFor("localhost", server.port());
clientUtilsMock = mockStatic(KubernetesClientUtils.class);
clientUtilsMock.when(KubernetesClientUtils::kubernetesApiClient)
.thenReturn(new ClientBuilder().setBasePath(server.baseUrl()).build());
stubData();
}
@AfterAll
static void teardown() {
clientUtilsMock.close();
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_profile;
import com.github.tomakehurst.wiremock.client.WireMock;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.reactive.server.WebTestClient;
/**
* Stud data is in
* {@link org.springframework.cloud.kubernetes.client.config.boostrap.stubs.LabeledConfigMapWithProfileConfigurationStub}
*
* @author wind57
*/
abstract class LabeledConfigMapWithProfileTests {
@Autowired
private WebTestClient webClient;
@AfterEach
void afterEach() {
WireMock.reset();
}
@AfterAll
static void afterAll() {
WireMock.shutdownServer();
}
/**
* <pre>
* this one is taken from : "blue.one". We find "color-configmap" by labels, and
* "color-configmap-k8s" exists, but "includeProfileSpecificSources=false", thus not taken.
* Since "explicitPrefix=blue", we take "blue.one"
* </pre>
*/
@Test
void testBlue() {
this.webClient.get().uri("/labeled-configmap/profile/blue").exchange().expectStatus().isOk()
.expectBody(String.class).value(Matchers.equalTo("1"));
}
/**
* <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.
* </pre>
*/
@Test
void testGreen() {
this.webClient.get().uri("/labeled-configmap/profile/green").exchange().expectStatus().isOk()
.expectBody(String.class).value(Matchers.equalTo("2#6#7"));
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_profile.controller;
import org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_profile.properties.Blue;
import org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_profile.properties.Green;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LabeledConfigMapWithProfileController {
private final Blue blue;
private final Green green;
public LabeledConfigMapWithProfileController(Blue blue, Green green) {
this.blue = blue;
this.green = green;
}
@GetMapping("/labeled-configmap/profile/blue")
public String blue() {
return blue.getOne();
}
@GetMapping("/labeled-configmap/profile/green")
public String green() {
return green.getTwo() + "#" + green.getSix() + "#" + green.getSeven();
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_profile.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("blue")
public class Blue {
private String one;
public String getOne() {
return one;
}
public void setOne(String one) {
this.one = one;
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_profile.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("green-configmap.green-configmap-k8s.green-configmap-prod")
public class Green {
private String two;
private String six;
private String seven;
public String getTwo() {
return two;
}
public void setTwo(String two) {
this.two = two;
}
public String getSix() {
return six;
}
public void setSix(String six) {
this.six = six;
}
public String getSeven() {
return seven;
}
public void setSeven(String seven) {
this.seven = seven;
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_profile;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_profile.properties.Blue;
import org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_profile.properties.Green;
@SpringBootApplication
@EnableConfigurationProperties({ Blue.class, Green.class })
public class LabeledSecretWithProfileApp {
public static void main(String[] args) {
SpringApplication.run(LabeledSecretWithProfileApp.class, args);
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_profile;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
/**
* @author wind57
*/
@ActiveProfiles({ "k8s", "prod" })
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = LabeledSecretWithProfileApp.class,
properties = { "spring.cloud.bootstrap.name=labeled-secret-with-profile",
"labeled.secret.with.profile.stub=true", "spring.main.cloud-platform=KUBERNETES",
"spring.cloud.bootstrap.enabled=true", "spring.cloud.kubernetes.client.namespace=spring-k8s" })
class LabeledSecretWithProfileBootstrapTests extends LabeledSecretWithProfileTests {
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_profile;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import io.kubernetes.client.util.ClientBuilder;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.mockito.MockedStatic;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.kubernetes.client.KubernetesClientUtils;
import org.springframework.test.context.ActiveProfiles;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
import static org.mockito.Mockito.mockStatic;
import static org.springframework.cloud.kubernetes.client.config.boostrap.stubs.LabeledSecretWithProfileConfigurationStub.stubData;
/**
* @author wind57
*/
@ActiveProfiles({ "k8s", "prod" })
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = LabeledSecretWithProfileApp.class,
properties = { "spring.application.name=labeled-secret-with-profile", "spring.main.cloud-platform=KUBERNETES",
"spring.config.import=kubernetes:,classpath:./labeled-secret-with-profile.yaml",
"spring.cloud.kubernetes.config.enabled=false" })
class LabeledSecretWithProfileConfigDataTests extends LabeledSecretWithProfileTests {
private static MockedStatic<KubernetesClientUtils> clientUtilsMock;
@BeforeAll
static void wireMock() {
WireMockServer server = new WireMockServer(options().dynamicPort());
server.start();
WireMock.configureFor("localhost", server.port());
clientUtilsMock = mockStatic(KubernetesClientUtils.class);
clientUtilsMock.when(KubernetesClientUtils::kubernetesApiClient)
.thenReturn(new ClientBuilder().setBasePath(server.baseUrl()).build());
stubData();
}
@AfterAll
static void teardown() {
clientUtilsMock.close();
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_profile;
import com.github.tomakehurst.wiremock.client.WireMock;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.reactive.server.WebTestClient;
/*
* <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>
*/
/**
* Stubs for this test are in
* {@link org.springframework.cloud.kubernetes.client.config.boostrap.stubs.LabeledSecretWithProfileConfigurationStub}
*
* @author wind57
*/
abstract class LabeledSecretWithProfileTests {
@Autowired
private WebTestClient webClient;
@AfterEach
public void afterEach() {
WireMock.reset();
}
@AfterAll
static void afterAll() {
WireMock.shutdownServer();
}
/**
* <pre>
* this one is taken from : "blue.one". We find "color-secret" by labels, and
* "color-secrets-k8s" exists, but "includeProfileSpecificSources=false", thus not taken.
* Since "explicitPrefix=blue", we take "blue.one"
* </pre>
*/
@Test
void testBlue() {
this.webClient.get().uri("/labeled-secret/profile/blue").exchange().expectStatus().isOk()
.expectBody(String.class).value(Matchers.equalTo("1"));
}
/**
* <pre>
* this one is taken from : "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.
* </pre>
*/
@Test
void testGreen() {
this.webClient.get().uri("/labeled-secret/profile/green").exchange().expectStatus().isOk()
.expectBody(String.class).value(Matchers.equalTo("2#6#7"));
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_profile.controller;
import org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_profile.properties.Blue;
import org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_profile.properties.Green;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LabeledSecretWithProfileController {
private final Blue blue;
private final Green green;
public LabeledSecretWithProfileController(Blue blue, Green green) {
this.blue = blue;
this.green = green;
}
@GetMapping("/labeled-secret/profile/blue")
public String blue() {
return blue.getOne();
}
@GetMapping("/labeled-secret/profile/green")
public String green() {
return green.getTwo() + "#" + green.getSix() + "#" + green.getSeven();
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_profile.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("blue")
public class Blue {
private String one;
public String getOne() {
return one;
}
public void setOne(String one) {
this.one = one;
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_profile.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("green-secret.green-secret-k8s.green-secret-prod")
public class Green {
private String two;
private String six;
private String seven;
public String getTwo() {
return two;
}
public void setTwo(String two) {
this.two = two;
}
public String getSix() {
return six;
}
public void setSix(String six) {
this.six = six;
}
public String getSeven() {
return seven;
}
public void setSeven(String seven) {
this.seven = seven;
}
}

View File

@@ -22,7 +22,7 @@ import org.springframework.boot.test.context.SpringBootTest;
* @author Ryan Baxter
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = NamedConfigMapWithPrefixApp.class,
properties = { "spring.cloud.bootstrap.name=named-config-map-with-prefix",
properties = { "spring.cloud.bootstrap.name=named-configmap-with-prefix",
"named.config.map.with.prefix.stub=true", "spring.main.cloud-platform=KUBERNETES",
"spring.cloud.bootstrap.enabled=true", "spring.cloud.kubernetes.client.namespace=spring-k8s" })
class NamedConfigMapWithPrefixBootstrapTests extends NamedConfigMapWithPrefixTests {

View File

@@ -36,7 +36,7 @@ import static org.springframework.cloud.kubernetes.client.config.boostrap.stubs.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = NamedConfigMapWithPrefixApp.class,
properties = { "spring.cloud.application.name=named-configmap-with-prefix",
"named.config.map.with.prefix.stub=true", "spring.main.cloud-platform=KUBERNETES",
"spring.config.import=kubernetes:,classpath:./named-config-map-with-prefix.yaml" })
"spring.config.import=kubernetes:,classpath:./named-configmap-with-prefix.yaml" })
class NamedConfigMapWithPrefixConfigDataTests extends NamedConfigMapWithPrefixTests {
private static MockedStatic<KubernetesClientUtils> clientUtilsMock;

View File

@@ -26,7 +26,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.reactive.server.WebTestClient;
/**
* The stub data for this test is in : ConfigMapNameAsPrefixConfigurationStub
* The stub data for this test is in :
* {@link org.springframework.cloud.kubernetes.client.config.boostrap.stubs.NamedConfigMapWithPrefixConfigurationStub}
*
* @author wind57
*/

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_profile;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_profile.properties.One;
import org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_profile.properties.Three;
import org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_profile.properties.Two;
/**
* The stub data for this test is in :
* {@link org.springframework.cloud.kubernetes.client.config.boostrap.stubs.NamedConfigMapWithProfileConfigurationStub}
*
* @author wind57
*/
@SpringBootApplication
@EnableConfigurationProperties({ One.class, Two.class, Three.class })
class NamedConfigMapWithProfileApp {
public static void main(String[] args) {
SpringApplication.run(NamedConfigMapWithProfileApp.class, args);
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_profile;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = NamedConfigMapWithProfileApp.class,
properties = { "spring.cloud.bootstrap.name=named-configmap-with-profile",
"named.config.map.with.profile.stub=true", "spring.main.cloud-platform=KUBERNETES",
"spring.cloud.bootstrap.enabled=true", "spring.cloud.kubernetes.client.namespace=spring-k8s" })
@ActiveProfiles("k8s")
class NamedConfigMapWithProfileBootstrapTests extends NamedConfigMapWithProfileTests {
}

View File

@@ -14,40 +14,33 @@
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config;
package org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_profile;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import io.kubernetes.client.util.ClientBuilder;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.MockedStatic;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.kubernetes.client.KubernetesClientUtils;
import org.springframework.cloud.kubernetes.client.config.applications.include_profile_specific_sources.IncludeProfileSpecificSourcesApp;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
import static org.mockito.Mockito.mockStatic;
import static org.springframework.cloud.kubernetes.client.config.boostrap.stubs.IncludeProfileSpecificSourcesConfigurationStub.stubData;
import static org.springframework.cloud.kubernetes.client.config.boostrap.stubs.NamedConfigMapWithProfileConfigurationStub.stubData;
/**
* @author Ryan Baxter
* @author wind57
*/
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = IncludeProfileSpecificSourcesApp.class,
properties = { "spring.application.name=include-profile-specific-sources",
"include.profile.specific.sources=true", "spring.main.cloud-platform=KUBERNETES",
"spring.config.import=kubernetes:,classpath:./include-profile-specific-sources.yaml" })
@AutoConfigureWebTestClient
@ActiveProfiles("dev")
public class KubernetesClientConfigMapConfigDataIncludeProfileSpecificSourcesTests
extends KubernetesClientConfigMapIncludeProfileSpecificSourcesTests {
classes = NamedConfigMapWithProfileApp.class,
properties = { "spring.application.name=named-config-map-with-profile", "include.profile.specific.sources=true",
"spring.main.cloud-platform=KUBERNETES",
"spring.config.import=kubernetes:,classpath:./named-configmap-with-profile.yaml" })
@ActiveProfiles("k8s")
class NamedConfigMapWithProfileConfigDataTests extends NamedConfigMapWithProfileTests {
private static MockedStatic<KubernetesClientUtils> clientUtilsMock;

View File

@@ -0,0 +1,101 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_profile;
import com.github.tomakehurst.wiremock.client.WireMock;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.reactive.server.WebTestClient;
/**
* The stub data for this test is in :
* {@link org.springframework.cloud.kubernetes.client.config.boostrap.stubs.NamedConfigMapWithProfileConfigurationStub}
*
* @author wind57
*/
abstract class NamedConfigMapWithProfileTests {
@Autowired
private WebTestClient webClient;
@AfterEach
public void afterEach() {
WireMock.reset();
}
@AfterAll
static void afterAll() {
WireMock.shutdownServer();
}
/**
* <pre>
* 'spring.cloud.kubernetes.config.useNameAsPrefix=true'
* 'spring.cloud.kubernetes.config.sources[0].useNameAsPrefix=false'
* 'spring.cloud.kubernetes.config.sources[0].includeProfileSpecificSources=true'
* ("one.property", "one-from-k8s")
*
* As such: @ConfigurationProperties("one"), value is overridden by the one that we read from
* the profile based source.
* </pre>
*/
@Test
void testOne() {
this.webClient.get().uri("/named-configmap/profile/one").exchange().expectStatus().isOk()
.expectBody(String.class).value(Matchers.equalTo("one-from-k8s"));
}
/**
* <pre>
* 'spring.cloud.kubernetes.config.useNameAsPrefix=true'
* 'spring.cloud.kubernetes.config.sources[1].explicitPrefix=two'
* 'spring.cloud.kubernetes.config.sources[1].includeProfileSpecificSources=false'
* ("property", "two")
*
* As such: @ConfigurationProperties("two").
*
* Even if there is a profile based source, we disabled reading it.
* </pre>
*/
@Test
void testTwo() {
this.webClient.get().uri("/named-configmap/profile/two").exchange().expectStatus().isOk()
.expectBody(String.class).value(Matchers.equalTo("two"));
}
/**
* <pre>
* 'spring.cloud.kubernetes.config.useNameAsPrefix=true'
* 'spring.cloud.kubernetes.config.sources[2].name=configmap-three'
* 'spring.cloud.kubernetes.config.sources[1].includeProfileSpecificSources=true'
* ("property", "three")
*
* As such: @ConfigurationProperties(prefix = "config-three"), value is overridden by the one that we read from
* the profile based source
* </pre>
*/
@Test
void testThree() {
this.webClient.get().uri("/named-configmap/profile/three").exchange().expectStatus().isOk()
.expectBody(String.class).value(Matchers.equalTo("three-from-k8s"));
}
}

View File

@@ -14,16 +14,16 @@
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.include_profile_specific_sources.controller;
package org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_profile.controller;
import org.springframework.cloud.kubernetes.client.config.applications.include_profile_specific_sources.properties.One;
import org.springframework.cloud.kubernetes.client.config.applications.include_profile_specific_sources.properties.Three;
import org.springframework.cloud.kubernetes.client.config.applications.include_profile_specific_sources.properties.Two;
import org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_profile.properties.One;
import org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_profile.properties.Three;
import org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_profile.properties.Two;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class IncludeProfileSpecificSourcesController {
public class NamedConfigMapWithProfileController {
private final One one;
@@ -31,23 +31,23 @@ public class IncludeProfileSpecificSourcesController {
private final Three three;
public IncludeProfileSpecificSourcesController(One one, Two two, Three three) {
public NamedConfigMapWithProfileController(One one, Two two, Three three) {
this.one = one;
this.two = two;
this.three = three;
}
@GetMapping("/profile-specific/one")
@GetMapping("/named-configmap/profile/one")
public String one() {
return one.getProperty();
}
@GetMapping("/profile-specific/two")
@GetMapping("/named-configmap/profile/two")
public String two() {
return two.getProperty();
}
@GetMapping("/profile-specific/three")
@GetMapping("/named-configmap/profile/three")
public String three() {
return three.getProperty();
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_profile.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("one")
public class One {
private String property;
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
}

View File

@@ -14,11 +14,11 @@
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.include_profile_specific_sources.properties;
package org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_profile.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("three")
@ConfigurationProperties("configmap-three")
public class Three {
private String property;

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_profile.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("two")
public class Two {
private String property;
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
}

View File

@@ -14,21 +14,21 @@
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.include_profile_specific_sources;
package org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_profile;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.kubernetes.client.config.applications.include_profile_specific_sources.properties.One;
import org.springframework.cloud.kubernetes.client.config.applications.include_profile_specific_sources.properties.Three;
import org.springframework.cloud.kubernetes.client.config.applications.include_profile_specific_sources.properties.Two;
import org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_profile.properties.One;
import org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_profile.properties.Three;
import org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_profile.properties.Two;
@SpringBootApplication
@EnableConfigurationProperties({ One.class, Two.class, Three.class })
public class IncludeProfileSpecificSourcesApp {
public class NamedSecretWithLabelApp {
public static void main(String[] args) {
SpringApplication.run(IncludeProfileSpecificSourcesApp.class, args);
SpringApplication.run(NamedSecretWithLabelApp.class, args);
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_profile;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
/**
* @author wind57
*/
@ActiveProfiles("k8s")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = NamedSecretWithLabelApp.class,
properties = { "spring.cloud.bootstrap.name=named-secret-with-profile", "named.secret.with.profile.stub=true",
"spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
"spring.cloud.kubernetes.client.namespace=spring-k8s" })
class NamedSecretWithProfileBootstrapTests extends NamedSecretWithProfileTests {
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_profile;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import io.kubernetes.client.util.ClientBuilder;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.mockito.MockedStatic;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.kubernetes.client.KubernetesClientUtils;
import org.springframework.test.context.ActiveProfiles;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
import static org.mockito.Mockito.mockStatic;
import static org.springframework.cloud.kubernetes.client.config.boostrap.stubs.NamedSecretWithProfileConfigurationStub.stubData;
/**
* @author wind57
*/
@ActiveProfiles("k8s")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = NamedSecretWithLabelApp.class,
properties = { "spring.cloud.application.name=named-secret-with-profile", "named.secret.with.profile.stub=true",
"spring.main.cloud-platform=KUBERNETES",
"spring.config.import=kubernetes:,classpath:./named-secret-with-profile.yaml",
"spring.cloud.kubernetes.client.namespace=spring-k8s" })
class NamedSecretWithProfileConfigDataTests extends NamedSecretWithProfileTests {
private static MockedStatic<KubernetesClientUtils> clientUtilsMock;
@BeforeAll
static void wireMock() {
WireMockServer server = new WireMockServer(options().dynamicPort());
server.start();
WireMock.configureFor("localhost", server.port());
clientUtilsMock = mockStatic(KubernetesClientUtils.class);
clientUtilsMock.when(KubernetesClientUtils::kubernetesApiClient)
.thenReturn(new ClientBuilder().setBasePath(server.baseUrl()).build());
stubData();
}
@AfterAll
static void teardown() {
clientUtilsMock.close();
}
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_profile;
import com.github.tomakehurst.wiremock.client.WireMock;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.reactive.server.WebTestClient;
/**
* The stub data for this test is in :
* {@link org.springframework.cloud.kubernetes.client.config.boostrap.stubs.NamedSecretWithProfileConfigurationStub}
*
* @author wind57
*/
abstract class NamedSecretWithProfileTests {
@Autowired
private WebTestClient webClient;
@AfterEach
void afterEach() {
WireMock.reset();
}
@AfterAll
static void afterAll() {
WireMock.shutdownServer();
}
/**
* <pre>
* 'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
* 'spring.cloud.kubernetes.secrets.sources[0].useNameAsPrefix=false'
* 'spring.cloud.kubernetes.secrets.sources[0].includeProfileSpecificSources=true'
* ("one.property", "one-from-k8s")
*
* As such: @ConfigurationProperties("one"), value is overridden by the one that we read from
* the profile based source.
* </pre>
*/
@Test
void testOne() {
this.webClient.get().uri("/named-secret/profile/one").exchange().expectStatus().isOk().expectBody(String.class)
.value(Matchers.equalTo("one-from-k8s"));
}
/**
* <pre>
* 'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
* 'spring.cloud.kubernetes.secrets.sources[1].explicitPrefix=two'
* 'spring.cloud.kubernetes.secrets.sources[1].includeProfileSpecificSources=false'
* ("property", "two")
*
* As such: @ConfigurationProperties("two").
*
* Even if there is a profile based source, we disabled reading it.
* </pre>
*/
@Test
void testTwo() {
this.webClient.get().uri("/named-secret/profile/two").exchange().expectStatus().isOk().expectBody(String.class)
.value(Matchers.equalTo("two"));
}
/**
* <pre>
* 'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
* 'spring.cloud.kubernetes.secrets.sources[2].name=secret-three'
* 'spring.cloud.kubernetes.secrets.sources[1].includeProfileSpecificSources=true'
* ("property", "three")
*
* As such: @ConfigurationProperties(prefix = "secret-three"), value is overridden by the one that we read from
* * the profile based source
* </pre>
*/
@Test
void testThree() {
this.webClient.get().uri("/named-secret/profile/three").exchange().expectStatus().isOk()
.expectBody(String.class).value(Matchers.equalTo("three-from-k8s"));
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_profile.controller;
import org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_profile.properties.One;
import org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_profile.properties.Three;
import org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_profile.properties.Two;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class NamedSecretWithProfileController {
private final One one;
private final Two two;
private final Three three;
public NamedSecretWithProfileController(One one, Two two, Three three) {
this.one = one;
this.two = two;
this.three = three;
}
@GetMapping("/named-secret/profile/one")
public String one() {
return one.getProperty();
}
@GetMapping("/named-secret/profile/two")
public String two() {
return two.getProperty();
}
@GetMapping("/named-secret/profile/three")
public String three() {
return three.getProperty();
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_profile.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("one")
public class One {
private String property;
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_profile.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "secret-three")
public class Three {
private String property;
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_profile.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("two")
public class Two {
private String property;
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.boostrap.stubs;
import java.util.Collections;
import java.util.Map;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.JSON;
import io.kubernetes.client.openapi.models.V1ConfigMap;
import io.kubernetes.client.openapi.models.V1ConfigMapBuilder;
import io.kubernetes.client.openapi.models.V1ConfigMapList;
import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder;
import io.kubernetes.client.util.ClientBuilder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
/**
* A test bootstrap that takes care to initialize ApiClient _before_ our main bootstrap
* context; with some stub data already present.
*
* @author wind57
*/
@Order(0)
@Configuration
@ConditionalOnProperty("labeled.config.map.with.prefix.stub")
public class LabeledConfigMapWithPrefixConfigurationStub {
@Bean
public WireMockServer wireMock() {
WireMockServer server = new WireMockServer(options().dynamicPort());
server.start();
WireMock.configureFor("localhost", server.port());
return server;
}
@Bean
public ApiClient apiClient(WireMockServer wireMockServer) {
ApiClient apiClient = new ClientBuilder().setBasePath("http://localhost:" + wireMockServer.port()).build();
io.kubernetes.client.openapi.Configuration.setDefaultApiClient(apiClient);
apiClient.setDebugging(true);
stubData();
return apiClient;
}
public static void stubData() {
V1ConfigMap one = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("configmap-one").withNamespace("spring-k8s")
.withLabels(Map.of("letter", "a")).build())
.addToData(Collections.singletonMap("one.property", "one")).build();
V1ConfigMap two = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("configmap-two").withNamespace("spring-k8s")
.withLabels(Map.of("letter", "b")).build())
.addToData(Collections.singletonMap("property", "two")).build();
V1ConfigMap three = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("configmap-three").withNamespace("spring-k8s")
.withLabels(Map.of("letter", "c")).build())
.addToData(Collections.singletonMap("property", "three")).build();
V1ConfigMap four = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("configmap-four").withNamespace("spring-k8s")
.withLabels(Map.of("letter", "d")).build())
.addToData(Collections.singletonMap("property", "four")).build();
// the actual stub for CoreV1Api calls
V1ConfigMapList configMapList = new V1ConfigMapList();
configMapList.addItemsItem(one);
configMapList.addItemsItem(two);
configMapList.addItemsItem(three);
configMapList.addItemsItem(four);
WireMock.stubFor(WireMock.get("/api/v1/namespaces/spring-k8s/configmaps")
.willReturn(WireMock.aResponse().withStatus(200).withBody(new JSON().serialize(configMapList))));
}
}

View File

@@ -0,0 +1,135 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.boostrap.stubs;
import java.util.Collections;
import java.util.Map;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.JSON;
import io.kubernetes.client.openapi.models.V1ConfigMap;
import io.kubernetes.client.openapi.models.V1ConfigMapBuilder;
import io.kubernetes.client.openapi.models.V1ConfigMapList;
import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder;
import io.kubernetes.client.util.ClientBuilder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
/**
* A test bootstrap that takes care to initialize ApiClient _before_ our main bootstrap
* context; with some stub data already present.
*
* @author wind57
*/
@Order(0)
@Configuration
@ConditionalOnProperty("labeled.config.map.with.profile.stub")
public class LabeledConfigMapWithProfileConfigurationStub {
@Bean
public WireMockServer wireMock() {
WireMockServer server = new WireMockServer(options().dynamicPort());
server.start();
WireMock.configureFor("localhost", server.port());
return server;
}
@Bean
public ApiClient apiClient(WireMockServer wireMockServer) {
ApiClient apiClient = new ClientBuilder().setBasePath("http://localhost:" + wireMockServer.port()).build();
io.kubernetes.client.openapi.Configuration.setDefaultApiClient(apiClient);
apiClient.setDebugging(true);
stubData();
return apiClient;
}
/*
* <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>
*/
public static void stubData() {
// is found by labels
V1ConfigMap colorConfigMap = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("color-configmap").withNamespace("spring-k8s")
.withLabels(Map.of("color", "blue")).build())
.addToData(Collections.singletonMap("one", "1")).build();
// is not taken, since "profileSpecificSources=false" for the above
V1ConfigMap colorConfigMapK8s = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("color-configmap-k8s").withNamespace("spring-k8s")
.withLabels(Map.of("color", "not-blue")).build())
.addToData(Collections.singletonMap("five", "5")).build();
// is found by labels
V1ConfigMap greenConfigMap = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("green-configmap").withNamespace("spring-k8s")
.withLabels(Map.of("color", "green")).build())
.addToData(Collections.singletonMap("two", "2")).build();
V1ConfigMap greenConfigMapK8s = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("green-configmap-k8s").withNamespace("spring-k8s")
.withLabels(Map.of("color", "green-k8s")).build())
.addToData(Collections.singletonMap("six", "6")).build();
// is taken because prod profile is active and "profileSpecificSources=true"
V1ConfigMap greenConfigMapProd = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("green-configmap-prod").withNamespace("spring-k8s")
.withLabels(Map.of("color", "green-prod")).build())
.addToData(Collections.singletonMap("seven", "7")).build();
// not taken
V1ConfigMap redConfigMap = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("red-configmap").withNamespace("spring-k8s")
.withLabels(Map.of("color", "red")).build())
.addToData(Collections.singletonMap("three", "3")).build();
// not taken
V1ConfigMap yellowConfigMap = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("yellow-configmap").withNamespace("spring-k8s")
.withLabels(Map.of("color", "yellow")).build())
.addToData(Collections.singletonMap("four", "4")).build();
// the actual stub for CoreV1Api calls
V1ConfigMapList configMaps = new V1ConfigMapList();
configMaps.addItemsItem(colorConfigMap);
configMaps.addItemsItem(colorConfigMapK8s);
configMaps.addItemsItem(greenConfigMap);
configMaps.addItemsItem(greenConfigMapK8s);
configMaps.addItemsItem(greenConfigMapProd);
configMaps.addItemsItem(redConfigMap);
configMaps.addItemsItem(yellowConfigMap);
WireMock.stubFor(WireMock.get("/api/v1/namespaces/spring-k8s/configmaps")
.willReturn(WireMock.aResponse().withStatus(200).withBody(new JSON().serialize(configMaps))));
}
}

View File

@@ -86,25 +86,14 @@ public class LabeledSecretWithPrefixConfigurationStub {
.addToData(Collections.singletonMap("property", "four".getBytes())).build();
// the actual stub for CoreV1Api calls
V1SecretList oneSecrets = new V1SecretList();
oneSecrets.addItemsItem(one);
WireMock.stubFor(WireMock.get("/api/v1/namespaces/spring-k8s/secrets?labelSelector=letter%3Da")
.willReturn(WireMock.aResponse().withStatus(200).withBody(new JSON().serialize(oneSecrets))));
V1SecretList secrets = new V1SecretList();
secrets.addItemsItem(one);
secrets.addItemsItem(two);
secrets.addItemsItem(three);
secrets.addItemsItem(four);
V1SecretList twoSecrets = new V1SecretList();
twoSecrets.addItemsItem(two);
WireMock.stubFor(WireMock.get("/api/v1/namespaces/spring-k8s/secrets?labelSelector=letter%3Db")
.willReturn(WireMock.aResponse().withStatus(200).withBody(new JSON().serialize(twoSecrets))));
V1SecretList threeSecrets = new V1SecretList();
threeSecrets.addItemsItem(three);
WireMock.stubFor(WireMock.get("/api/v1/namespaces/spring-k8s/secrets?labelSelector=letter%3Dc")
.willReturn(WireMock.aResponse().withStatus(200).withBody(new JSON().serialize(threeSecrets))));
V1SecretList fourSecrets = new V1SecretList();
fourSecrets.addItemsItem(four);
WireMock.stubFor(WireMock.get("/api/v1/namespaces/spring-k8s/secrets?labelSelector=letter%3Dd")
.willReturn(WireMock.aResponse().withStatus(200).withBody(new JSON().serialize(fourSecrets))));
WireMock.stubFor(WireMock.get("/api/v1/namespaces/spring-k8s/secrets")
.willReturn(WireMock.aResponse().withStatus(200).withBody(new JSON().serialize(secrets))));
}
}

View File

@@ -0,0 +1,134 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.boostrap.stubs;
import java.util.Collections;
import java.util.Map;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.JSON;
import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder;
import io.kubernetes.client.openapi.models.V1Secret;
import io.kubernetes.client.openapi.models.V1SecretBuilder;
import io.kubernetes.client.openapi.models.V1SecretList;
import io.kubernetes.client.util.ClientBuilder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
/**
* A test bootstrap that takes care to initialize ApiClient _before_ our main bootstrap
* context; with some stub data already present.
*
* @author wind57
*/
@Order(0)
@Configuration
@ConditionalOnProperty("labeled.secret.with.profile.stub")
public class LabeledSecretWithProfileConfigurationStub {
@Bean
public WireMockServer wireMock() {
WireMockServer server = new WireMockServer(options().dynamicPort());
server.start();
WireMock.configureFor("localhost", server.port());
return server;
}
@Bean
public ApiClient apiClient(WireMockServer wireMockServer) {
ApiClient apiClient = new ClientBuilder().setBasePath("http://localhost:" + wireMockServer.port()).build();
io.kubernetes.client.openapi.Configuration.setDefaultApiClient(apiClient);
apiClient.setDebugging(true);
stubData();
return apiClient;
}
/*
* <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>
*/
public static void stubData() {
// is found by labels
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();
// 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();
// 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();
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();
// 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();
// 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();
// 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();
// the actual stub for CoreV1Api calls
V1SecretList secrets = new V1SecretList();
secrets.addItemsItem(colorSecret);
secrets.addItemsItem(colorSecretK8s);
secrets.addItemsItem(greenSecret);
secrets.addItemsItem(greenSecretK8s);
secrets.addItemsItem(shapeSecretProd);
secrets.addItemsItem(redSecret);
secrets.addItemsItem(yellowSecret);
WireMock.stubFor(WireMock.get("/api/v1/namespaces/spring-k8s/secrets")
.willReturn(WireMock.aResponse().withStatus(200).withBody(new JSON().serialize(secrets))));
}
}

View File

@@ -44,8 +44,8 @@ import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options
*/
@Order(0)
@Configuration
@ConditionalOnProperty("include.profile.specific.sources")
public class IncludeProfileSpecificSourcesConfigurationStub {
@ConditionalOnProperty("named.config.map.with.profile.stub")
public class NamedConfigMapWithProfileConfigurationStub {
@Bean
public WireMockServer wireMock() {
@@ -65,33 +65,36 @@ public class IncludeProfileSpecificSourcesConfigurationStub {
}
public static void stubData() {
V1ConfigMap one = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("config-map-one-dev").withNamespace("spring-k8s")
.withResourceVersion("1").build())
.withMetadata(new V1ObjectMetaBuilder().withName("configmap-one").withNamespace("spring-k8s").build())
.addToData(Collections.singletonMap("one.property", "one")).build();
V1ConfigMap two = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("config-map-two").withNamespace("spring-k8s")
.withResourceVersion("1").build())
.addToData(Collections.singletonMap("two.property", "two")).build();
V1ConfigMap oneFromK8s = new V1ConfigMapBuilder()
.withMetadata(
new V1ObjectMetaBuilder().withName("configmap-one-k8s").withNamespace("spring-k8s").build())
.addToData(Collections.singletonMap("one.property", "one-from-k8s")).build();
V1ConfigMap twoDev = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("config-map-two-dev").withNamespace("spring-k8s")
.withResourceVersion("1").build())
.addToData(Collections.singletonMap("two.property", "twoDev")).build();
V1ConfigMap two = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("configmap-two").withNamespace("spring-k8s").build())
.addToData(Collections.singletonMap("property", "two")).build();
V1ConfigMap twoFromK8s = new V1ConfigMapBuilder()
.withMetadata(
new V1ObjectMetaBuilder().withName("configmap-two-k8s").withNamespace("spring-k8s").build())
.addToData(Collections.singletonMap("property", "two-from-k8s")).build();
V1ConfigMap three = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("config-map-three").withNamespace("spring-k8s")
.withResourceVersion("1").build())
.addToData(Collections.singletonMap("three.property", "three")).build();
.withMetadata(new V1ObjectMetaBuilder().withName("configmap-three").withNamespace("spring-k8s").build())
.addToData(Collections.singletonMap("property", "three")).build();
V1ConfigMap threeDev = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("config-map-three-dev").withNamespace("spring-k8s")
.withResourceVersion("1").build())
.addToData(Collections.singletonMap("three.property", "threeDev")).build();
V1ConfigMap threeFromK8s = new V1ConfigMapBuilder()
.withMetadata(
new V1ObjectMetaBuilder().withName("configmap-three-k8s").withNamespace("spring-k8s").build())
.addToData(Collections.singletonMap("property", "three-from-k8s")).build();
V1ConfigMapList allConfigMaps = new V1ConfigMapList();
allConfigMaps.setItems(Arrays.asList(one, two, twoDev, three, threeDev));
allConfigMaps.setItems(Arrays.asList(one, oneFromK8s, two, twoFromK8s, three, threeFromK8s));
// the actual stub for CoreV1Api calls
WireMock.stubFor(WireMock.get("/api/v1/namespaces/spring-k8s/configmaps")

View File

@@ -59,7 +59,6 @@ public class NamedSecretWithPrefixConfigurationStub {
public ApiClient apiClient(WireMockServer wireMockServer) {
ApiClient apiClient = new ClientBuilder().setBasePath("http://localhost:" + wireMockServer.port()).build();
io.kubernetes.client.openapi.Configuration.setDefaultApiClient(apiClient);
apiClient.setDebugging(true);
stubData();
return apiClient;
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.config.boostrap.stubs;
import java.util.Arrays;
import java.util.Collections;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.JSON;
import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder;
import io.kubernetes.client.openapi.models.V1Secret;
import io.kubernetes.client.openapi.models.V1SecretBuilder;
import io.kubernetes.client.openapi.models.V1SecretList;
import io.kubernetes.client.util.ClientBuilder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
/**
* A test bootstrap that takes care to initialize ApiClient _before_ our main bootstrap
* context; with some stub data already present.
*
* @author wind57
*/
@Order(0)
@Configuration
@ConditionalOnProperty("named.secret.with.profile.stub")
public class NamedSecretWithProfileConfigurationStub {
@Bean
public WireMockServer wireMock() {
WireMockServer server = new WireMockServer(options().dynamicPort());
server.start();
WireMock.configureFor("localhost", server.port());
return server;
}
@Bean
public ApiClient apiClient(WireMockServer wireMockServer) {
ApiClient apiClient = new ClientBuilder().setBasePath("http://localhost:" + wireMockServer.port()).build();
io.kubernetes.client.openapi.Configuration.setDefaultApiClient(apiClient);
stubData();
return apiClient;
}
public static void stubData() {
V1Secret one = new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("secret-one").withNamespace("spring-k8s")
.withResourceVersion("1").build())
.addToData(Collections.singletonMap("one.property", "one".getBytes())).build();
V1Secret oneWithProfile = new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("secret-one-k8s").withNamespace("spring-k8s")
.withResourceVersion("1").build())
.addToData(Collections.singletonMap("one.property", "one-from-k8s".getBytes())).build();
V1Secret two = new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("secret-two").withNamespace("spring-k8s")
.withResourceVersion("1").build())
.addToData(Collections.singletonMap("property", "two".getBytes())).build();
V1Secret twoWithProfile = new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("secret-two-k8s").withNamespace("spring-k8s")
.withResourceVersion("1").build())
.addToData(Collections.singletonMap("property", "two-from-k8s".getBytes())).build();
V1Secret three = new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("secret-three").withNamespace("spring-k8s")
.withResourceVersion("1").build())
.addToData(Collections.singletonMap("property", "three".getBytes())).build();
V1Secret threeFromProfile = new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("secret-three-k8s").withNamespace("spring-k8s")
.withResourceVersion("1").build())
.addToData(Collections.singletonMap("property", "three-from-k8s".getBytes())).build();
V1SecretList allSecrets = new V1SecretList();
allSecrets.setItems(Arrays.asList(one, oneWithProfile, two, twoWithProfile, three, threeFromProfile));
// the actual stub for CoreV1Api calls
WireMock.stubFor(WireMock.get("/api/v1/namespaces/spring-k8s/secrets")
.willReturn(WireMock.aResponse().withStatus(200).withBody(new JSON().serialize(allSecrets))));
}
}

View File

@@ -98,8 +98,7 @@ abstract class ConfigFailFastEnabledButRetryDisabled {
assertThat(context.containsBean("kubernetesConfigRetryInterceptor")).isFalse();
assertThatThrownBy(() -> propertySourceLocator.locate(new MockEnvironment()))
.isInstanceOf(IllegalStateException.class)
.hasMessage("Unable to read ConfigMap(s) in namespace 'default'");
.isInstanceOf(IllegalStateException.class).hasMessage("Internal Server Error");
// verify that propertySourceLocator.locate is called only once
verify(propertySourceLocator, times(1)).locate(any());

View File

@@ -110,8 +110,7 @@ abstract class ConfigRetryDisabledButSecretsRetryEnabled {
// TODO not in bootstrap
// assertThat(context.containsBean("kubernetesConfigRetryInterceptor")).isTrue();
assertThatThrownBy(() -> propertySourceLocator.locate(new MockEnvironment()))
.isInstanceOf(IllegalStateException.class)
.hasMessage("Unable to read ConfigMap(s) in namespace 'default'");
.isInstanceOf(IllegalStateException.class).hasMessage("Internal Server Error");
// verify that propertySourceLocator.locate is called only once
verify(propertySourceLocator, times(1)).locate(any());

View File

@@ -159,8 +159,7 @@ abstract class ConfigRetryEnabled {
stubFor(get(API).willReturn(aResponse().withStatus(500).withBody("Internal Server Error")));
assertThatThrownBy(() -> propertySourceLocator.locate(new MockEnvironment()))
.isInstanceOf(IllegalStateException.class)
.hasMessage("Unable to read ConfigMap(s) in namespace 'default'");
.isInstanceOf(IllegalStateException.class).hasMessage("Internal Server Error");
// verify the request was retried 5 times
WireMock.verify(5, getRequestedFor(urlEqualTo(API)));

View File

@@ -110,8 +110,7 @@ class SecretsFailFastEnabledButRetryDisabled {
assertThat(context.containsBean("kubernetesSecretsRetryInterceptor")).isFalse();
assertThatThrownBy(() -> propertySourceLocator.locate(new MockEnvironment()))
.isInstanceOf(IllegalStateException.class)
.hasMessage("Unable to read Secret with name 'my-secret' in namespace 'default'");
.isInstanceOf(IllegalStateException.class).hasMessage("Internal Server Error");
// verify that propertySourceLocator.locate is called only once
verify(propertySourceLocator, times(1)).locate(any());

View File

@@ -118,8 +118,7 @@ class SecretsRetryDisabledButConfigRetryEnabled {
// TODO not in bootstrap
// assertThat(context.containsBean("kubernetesSecretsRetryInterceptor")).isTrue();
assertThatThrownBy(() -> propertySourceLocator.locate(new MockEnvironment()))
.isInstanceOf(IllegalStateException.class)
.hasMessage("Unable to read Secret with name 'my-secret' in namespace 'default'");
.isInstanceOf(IllegalStateException.class).hasMessage("Internal Server Error");
// verify that propertySourceLocator.locate is called only once
verify(propertySourceLocator, times(1)).locate(any());

View File

@@ -167,8 +167,7 @@ class SecretsRetryEnabled {
stubFor(get(API).willReturn(aResponse().withStatus(500).withBody("Internal Server Error")));
assertThatThrownBy(() -> propertySourceLocator.locate(new MockEnvironment()))
.isInstanceOf(IllegalStateException.class)
.hasMessage("Unable to read Secret with name 'my-secret' in namespace 'default'");
.isInstanceOf(IllegalStateException.class).hasMessage("Internal Server Error");
// verify retried 5 times until failure
WireMock.verify(5, getRequestedFor(urlEqualTo(API)));

View File

@@ -1,6 +1,10 @@
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.kubernetes.client.config.boostrap.stubs.IncludeProfileSpecificSourcesConfigurationStub, \
org.springframework.cloud.kubernetes.client.config.boostrap.stubs.NamedConfigMapWithProfileConfigurationStub, \
org.springframework.cloud.kubernetes.client.config.boostrap.stubs.NamedConfigMapWithPrefixConfigurationStub, \
org.springframework.cloud.kubernetes.client.config.boostrap.stubs.NamedSecretWithPrefixConfigurationStub, \
org.springframework.cloud.kubernetes.client.config.boostrap.stubs.NamedSecretWithProfileConfigurationStub, \
org.springframework.cloud.kubernetes.client.config.boostrap.stubs.LabeledSecretWithPrefixConfigurationStub, \
org.springframework.cloud.kubernetes.client.config.boostrap.stubs.NamedSecretWithPrefixConfigurationStub, \
org.springframework.cloud.kubernetes.client.config.boostrap.stubs.LabeledSecretWithProfileConfigurationStub, \
org.springframework.cloud.kubernetes.client.config.boostrap.stubs.LabeledConfigMapWithPrefixConfigurationStub, \
org.springframework.cloud.kubernetes.client.config.boostrap.stubs.LabeledConfigMapWithProfileConfigurationStub, \
org.springframework.cloud.kubernetes.client.config.EnableRetryBootstrapConfiguration

View File

@@ -0,0 +1,21 @@
spring:
application:
name: labeled-configmap-with-prefix
cloud:
kubernetes:
config:
enableApi: true
useNameAsPrefix: true
namespace: spring-k8s
sources:
- labels:
letter: a
useNameAsPrefix: false
- labels:
letter: b
explicitPrefix: two
- labels:
letter: c
- labels:
letter: d
useNameAsPrefix: true

View File

@@ -0,0 +1,22 @@
spring:
application:
name: labeled-configmap-with-profile
cloud:
kubernetes:
config:
enableApi: true
useNameAsPrefix: true
namespace: spring-k8s
includeProfileSpecificSources: true
sources:
- labels:
color: blue
explicitPrefix: blue
includeProfileSpecificSources: false
- labels:
color: green
- labels:
color: red
- labels:
color: yellow
useNameAsPrefix: true

View File

@@ -0,0 +1,22 @@
spring:
application:
name: labeled-secret-with-profile
cloud:
kubernetes:
secrets:
enableApi: true
useNameAsPrefix: true
namespace: spring-k8s
includeProfileSpecificSources: true
sources:
- labels:
color: blue
explicitPrefix: blue
includeProfileSpecificSources: false
- labels:
color: green
- labels:
color: red
- labels:
color: yellow
useNameAsPrefix: true

View File

@@ -0,0 +1,17 @@
spring:
application:
name: named-configmap-with-profile
cloud:
kubernetes:
config:
enableApi: true
useNameAsPrefix: true
namespace: spring-k8s
includeProfileSpecificSources: true
sources:
- name: configmap-one
useNameAsPrefix: false
- name: configmap-two
explicitPrefix: two
includeProfileSpecificSources: false
- name: configmap-three

View File

@@ -0,0 +1,18 @@
spring:
application:
name: named-secret-with-prefix
cloud:
kubernetes:
client:
namespace: spring-k8s
secrets:
enableApi: true
useNameAsPrefix: true
includeProfileSpecificSources: true
sources:
- name: secret-one
useNameAsPrefix: false
- name: secret-two
explicitPrefix: two
includeProfileSpecificSources: false
- name: secret-three

View File

@@ -31,7 +31,7 @@ import org.springframework.retry.support.RetryTemplate;
*/
public class ConfigDataRetryableConfigMapPropertySourceLocator extends ConfigMapPropertySourceLocator {
private RetryTemplate retryTemplate;
private final RetryTemplate retryTemplate;
private ConfigMapPropertySourceLocator configMapPropertySourceLocator;

View File

@@ -16,13 +16,13 @@
package org.springframework.cloud.kubernetes.commons.config;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.util.stream.Stream;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.env.Environment;
@@ -40,18 +40,18 @@ import static org.springframework.cloud.kubernetes.commons.config.ConfigUtils.ge
public class ConfigMapConfigProperties extends AbstractConfigProperties {
/**
* Prefix for Kubernetes secrets configuration properties.
* Prefix for Kubernetes config maps configuration properties.
*/
public static final String PREFIX = "spring.cloud.kubernetes.config";
private static final Log LOG = LogFactory.getLog(ConfigMapConfigProperties.class);
private boolean enableApi = true;
private List<String> paths = Collections.emptyList();
private List<Source> sources = Collections.emptyList();
private Map<String, String> labels = Collections.emptyMap();
public boolean isEnableApi() {
return this.enableApi;
}
@@ -76,28 +76,34 @@ public class ConfigMapConfigProperties extends AbstractConfigProperties {
this.sources = sources;
}
public Map<String, String> getLabels() {
return labels;
}
public void setLabels(Map<String, String> labels) {
this.labels = labels;
}
/**
* @return A list of Source to use. If the user has not specified any Source
* properties, then a single Source is constructed based on the supplied name and
* namespace.
*
* These are the actual name/namespace pairs that are used to create a
* ConfigMapPropertySource.
* @return A list of config map source(s) to use.
*/
public List<NormalizedSource> determineSources(Environment environment) {
if (this.sources.isEmpty()) {
if (useNameAsPrefix) {
LOG.warn(
"'spring.cloud.kubernetes.config.useNameAsPrefix' is set to 'true', but 'spring.cloud.kubernetes.config.sources'"
+ " is empty; as such will default 'useNameAsPrefix' to 'false'");
List<NormalizedSource> result = new ArrayList<>(2);
String name = getApplicationName(environment, this.name, "ConfigMap");
result.add(new NamedConfigMapNormalizedSource(name, this.namespace, this.failFast,
this.includeProfileSpecificSources));
if (!labels.isEmpty()) {
result.add(new LabeledConfigMapNormalizedSource(this.namespace, this.labels, this.failFast,
ConfigUtils.Prefix.DEFAULT, false));
}
String name = getApplicationName(environment, this.name, "Config Map");
return Collections.singletonList(
new NamedConfigMapNormalizedSource(name, namespace, failFast, includeProfileSpecificSources));
return result;
}
return sources.stream()
.map(s -> s.normalize(name, namespace, useNameAsPrefix, includeProfileSpecificSources, failFast))
return this.sources
.stream().flatMap(s -> s.normalize(this.name, this.namespace, this.labels,
this.includeProfileSpecificSources, this.failFast, this.useNameAsPrefix, environment))
.collect(Collectors.toList());
}
@@ -116,6 +122,16 @@ public class ConfigMapConfigProperties extends AbstractConfigProperties {
*/
private String namespace;
/**
* labels of the config map to look for against.
*/
private Map<String, String> labels = Collections.emptyMap();
/**
* An explicit prefix to be used for properties.
*/
private String explicitPrefix;
/**
* Use config map name as prefix for properties. Can't be a primitive, we need to
* know if it was explicitly set or not
@@ -128,11 +144,6 @@ public class ConfigMapConfigProperties extends AbstractConfigProperties {
*/
protected Boolean includeProfileSpecificSources;
/**
* An explicit prefix to be used for properties.
*/
private String explicitPrefix;
public Source() {
}
@@ -157,6 +168,10 @@ public class ConfigMapConfigProperties extends AbstractConfigProperties {
return useNameAsPrefix;
}
public Boolean getUseNameAsPrefix() {
return useNameAsPrefix;
}
public void setUseNameAsPrefix(Boolean useNameAsPrefix) {
this.useNameAsPrefix = useNameAsPrefix;
}
@@ -177,20 +192,47 @@ public class ConfigMapConfigProperties extends AbstractConfigProperties {
this.includeProfileSpecificSources = includeProfileSpecificSources;
}
public Map<String, String> getLabels() {
return labels;
}
public void setLabels(Map<String, String> labels) {
this.labels = labels;
}
public boolean isEmpty() {
return !StringUtils.hasLength(this.name) && !StringUtils.hasLength(this.namespace);
}
private NormalizedSource normalize(String defaultName, String defaultNamespace, boolean defaultUseNameAsPrefix,
boolean defaultIncludeProfileSpecificSources, boolean failFast) {
private Stream<NormalizedSource> normalize(String defaultName, String defaultNamespace,
Map<String, String> defaultLabels, boolean defaultIncludeProfileSpecificSources, boolean failFast,
boolean defaultUseNameAsPrefix, Environment environment) {
Stream.Builder<NormalizedSource> normalizedSources = Stream.builder();
String normalizedName = StringUtils.hasLength(this.name) ? this.name : defaultName;
String normalizedNamespace = StringUtils.hasLength(this.namespace) ? this.namespace : defaultNamespace;
ConfigUtils.Prefix prefix = ConfigUtils.findPrefix(this.explicitPrefix, useNameAsPrefix,
Map<String, String> normalizedLabels = this.labels.isEmpty() ? defaultLabels : this.labels;
String configMapName = getApplicationName(environment, normalizedName, "Config Map");
ConfigUtils.Prefix prefix = ConfigUtils.findPrefix(this.explicitPrefix, this.useNameAsPrefix,
defaultUseNameAsPrefix, normalizedName);
boolean includeProfileSpecificSources = ConfigUtils.includeProfileSpecificSources(
defaultIncludeProfileSpecificSources, this.includeProfileSpecificSources);
return new NamedConfigMapNormalizedSource(normalizedName, normalizedNamespace, failFast, prefix,
includeProfileSpecificSources);
NormalizedSource namedBasedSource = new NamedConfigMapNormalizedSource(configMapName, normalizedNamespace,
failFast, prefix, includeProfileSpecificSources);
normalizedSources.add(namedBasedSource);
if (!normalizedLabels.isEmpty()) {
NormalizedSource labeledBasedSource = new LabeledConfigMapNormalizedSource(normalizedNamespace, labels,
failFast, prefix, includeProfileSpecificSources);
normalizedSources.add(labeledBasedSource);
}
return normalizedSources.build();
}
@Override

View File

@@ -69,7 +69,7 @@ public abstract class ConfigMapPropertySourceLocator implements PropertySourceLo
if (this.properties.isEnableApi()) {
Set<NormalizedSource> sources = new LinkedHashSet<>(this.properties.determineSources(environment));
LOG.debug("Config Map normalized sources : " + sources);
sources.forEach(s -> composite.addFirstPropertySource(getMapPropertySourceForSingleConfigMap(env, s)));
sources.forEach(s -> composite.addFirstPropertySource(getMapPropertySource(s, env)));
}
addPropertySourcesFromPaths(environment, composite);
@@ -84,12 +84,6 @@ public abstract class ConfigMapPropertySourceLocator implements PropertySourceLo
return PropertySourceLocator.super.locateCollection(environment);
}
private MapPropertySource getMapPropertySourceForSingleConfigMap(ConfigurableEnvironment environment,
NormalizedSource normalizedSource) {
return getMapPropertySource(normalizedSource, environment);
}
private void addPropertySourcesFromPaths(Environment environment, CompositePropertySource composite) {
Set<String> uniquePaths = new LinkedHashSet<>(properties.getPaths());
uniquePaths.stream().map(Paths::get).filter(p -> {

View File

@@ -16,8 +16,16 @@
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.Supplier;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -125,11 +133,11 @@ public final class ConfigUtils {
/**
* action to take when an Exception happens when dealing with a source.
*/
public static void onException(boolean failFast, String message, Exception e) {
public static void onException(boolean failFast, Exception e) {
if (failFast) {
throw new IllegalStateException(message, e);
throw new IllegalStateException(e.getMessage(), e);
}
LOG.warn(message + ". Ignoring.", e);
LOG.warn(e.getMessage() + ". Ignoring.", e);
}
/*
@@ -141,7 +149,8 @@ public final class ConfigUtils {
Map<String, Object> withPrefix = CollectionUtils.newHashMap(context.data().size());
context.data().forEach((key, value) -> withPrefix.put(context.prefix() + "." + key, value));
String propertySourceTokens = String.join(PROPERTY_SOURCE_NAME_SEPARATOR, context.propertySourceNames());
String propertySourceTokens = String.join(PROPERTY_SOURCE_NAME_SEPARATOR,
context.propertySourceNames().stream().sorted().collect(Collectors.toCollection(LinkedHashSet::new)));
return new SourceData(sourceName(target, propertySourceTokens, context.namespace()), withPrefix);
}
@@ -149,6 +158,104 @@ public final class ConfigUtils {
return target + PROPERTY_SOURCE_NAME_SEPARATOR + applicationName + PROPERTY_SOURCE_NAME_SEPARATOR + namespace;
}
/**
* 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<StrippedSourceContainer> strippedSources,
Environment environment, Set<String> sourceNames, String namespace, boolean decode) {
Set<String> foundSourceNames = new HashSet<>();
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));
});
return new MultipleSourcesContainer(foundSourceNames, data);
}
/**
* 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). This method first searches by labels, find the sources, then uses
* these names to find any profile based sources.
*/
public static MultipleSourcesContainer processLabeledData(List<StrippedSourceContainer> containers,
Environment environment, Map<String, String> labels, String namespace, Set<String> profiles,
boolean decode) {
// find sources by provided labels
List<StrippedSourceContainer> sourcesByLabels = 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)
List<String> sourceNamesByLabelsWithProfile = new ArrayList<>();
if (profiles != null && !profiles.isEmpty()) {
for (StrippedSourceContainer one : sourcesByLabels) {
for (String profile : profiles) {
String name = one.name() + "-" + profile;
sourceNamesByLabelsWithProfile.add(name);
}
}
}
// 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);
Set<String> sourceNames = new HashSet<>();
Map<String, Object> result = new HashMap<>();
sourcesToTake.forEach(source -> {
String foundSourceName = source.name();
LOG.debug("Loaded source with name : '" + foundSourceName + " in namespace: '" + namespace + "'");
sourceNames.add(foundSourceName);
Map<String, String> rawData = source.data();
if (decode) {
rawData = decodeData(rawData);
}
result.putAll(SourceDataEntriesProcessor.processAllEntries(rawData, environment));
});
return new MultipleSourcesContainer(sourceNames, result);
}
public static boolean noSources(List<?> sources, String namespace) {
if (sources == null || sources.isEmpty()) {
LOG.debug("No sources in namespace '" + namespace + "'");
return true;
}
return false;
}
private static Map<String, String> decodeData(Map<String, String> data) {
Map<String, String> result = new HashMap<>(CollectionUtils.newHashMap(data.size()));
data.forEach((key, value) -> result.put(key, new String(Base64.getDecoder().decode(value)).trim()));
return result;
}
public static final class Prefix {
/**

View File

@@ -0,0 +1,99 @@
/*
* Copyright 2013-2022 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.commons.config;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
/**
* A config map source that is based on labels.
*
* @author wind57
*/
public final class LabeledConfigMapNormalizedSource extends NormalizedSource {
private final Map<String, String> labels;
private final ConfigUtils.Prefix prefix;
private final boolean includeProfileSpecificSources;
public LabeledConfigMapNormalizedSource(String namespace, Map<String, String> labels, boolean failFast,
ConfigUtils.Prefix prefix, boolean includeProfileSpecificSources) {
super(null, namespace, failFast);
this.labels = Collections.unmodifiableMap(Objects.requireNonNull(labels));
this.prefix = Objects.requireNonNull(prefix);
this.includeProfileSpecificSources = includeProfileSpecificSources;
}
public LabeledConfigMapNormalizedSource(String namespace, Map<String, String> labels, boolean failFast,
boolean includeProfileSpecificSources) {
super(null, namespace, failFast);
this.labels = Collections.unmodifiableMap(Objects.requireNonNull(labels));
this.prefix = ConfigUtils.Prefix.DEFAULT;
this.includeProfileSpecificSources = includeProfileSpecificSources;
}
/**
* will return an immutable Map.
*/
public Map<String, String> labels() {
return labels;
}
public ConfigUtils.Prefix prefix() {
return prefix;
}
public boolean profileSpecificSources() {
return this.includeProfileSpecificSources;
}
@Override
public NormalizedSourceType type() {
return NormalizedSourceType.LABELED_CONFIG_MAP;
}
@Override
public String target() {
return "configmap";
}
@Override
public String toString() {
return "{ config map labels : '" + labels() + "', namespace : '" + namespace() + "'";
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
LabeledConfigMapNormalizedSource other = (LabeledConfigMapNormalizedSource) o;
return Objects.equals(labels(), other.labels()) && Objects.equals(namespace(), other.namespace());
}
@Override
public int hashCode() {
return Objects.hash(labels(), namespace());
}
}

View File

@@ -31,17 +31,22 @@ public final class LabeledSecretNormalizedSource extends NormalizedSource {
private final ConfigUtils.Prefix prefix;
private final boolean includeProfileSpecificSources;
public LabeledSecretNormalizedSource(String namespace, Map<String, String> labels, boolean failFast,
ConfigUtils.Prefix prefix) {
ConfigUtils.Prefix prefix, boolean includeProfileSpecificSources) {
super(null, namespace, failFast);
this.labels = Collections.unmodifiableMap(Objects.requireNonNull(labels));
this.prefix = Objects.requireNonNull(prefix);
this.includeProfileSpecificSources = includeProfileSpecificSources;
}
public LabeledSecretNormalizedSource(String namespace, Map<String, String> labels, boolean failFast) {
public LabeledSecretNormalizedSource(String namespace, Map<String, String> labels, boolean failFast,
boolean includeProfileSpecificSources) {
super(null, namespace, failFast);
this.labels = Collections.unmodifiableMap(Objects.requireNonNull(labels));
this.prefix = ConfigUtils.Prefix.DEFAULT;
this.includeProfileSpecificSources = includeProfileSpecificSources;
}
/**
@@ -55,6 +60,10 @@ public final class LabeledSecretNormalizedSource extends NormalizedSource {
return prefix;
}
public boolean profileSpecificSources() {
return this.includeProfileSpecificSources;
}
@Override
public NormalizedSourceType type() {
return NormalizedSourceType.LABELED_SECRET;

View File

@@ -0,0 +1,92 @@
/*
* Copyright 2013-2022 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.commons.config;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static org.springframework.cloud.kubernetes.commons.config.ConfigUtils.onException;
import static org.springframework.cloud.kubernetes.commons.config.Constants.PROPERTY_SOURCE_NAME_SEPARATOR;
/**
* @author wind57
*
* Base class when dealing with labeled sources that support profiles specific sources,
* prefix based properties and single file yaml/properties.
*/
public abstract class LabeledSourceData {
public final SourceData compute(Map<String, String> labels, ConfigUtils.Prefix prefix, String target,
boolean profileSources, boolean failFast, String namespace, String[] activeProfiles) {
MultipleSourcesContainer data = MultipleSourcesContainer.empty();
try {
Set<String> profiles = Set.of();
if (profileSources) {
profiles = Arrays.stream(activeProfiles).collect(Collectors.toSet());
}
data = dataSupplier(labels, profiles);
// need this check because when there is no data, the name of the property
// source
// is using provided labels,
// unlike when the data is present: when we use secret names
if (data.names().isEmpty()) {
String names = labels.keySet().stream().sorted()
.collect(Collectors.joining(PROPERTY_SOURCE_NAME_SEPARATOR));
return SourceData.emptyRecord(ConfigUtils.sourceName(target, names, namespace));
}
if (prefix != ConfigUtils.Prefix.DEFAULT) {
String prefixToUse;
if (prefix == ConfigUtils.Prefix.KNOWN) {
prefixToUse = prefix.prefixProvider().get();
}
else {
prefixToUse = data.names().stream().sorted()
.collect(Collectors.joining(PROPERTY_SOURCE_NAME_SEPARATOR));
}
PrefixContext prefixContext = new PrefixContext(data.data(), prefixToUse, namespace, data.names());
return ConfigUtils.withPrefix(target, prefixContext);
}
}
catch (Exception e) {
onException(failFast, e);
}
String names = data.names().stream().sorted().collect(Collectors.joining(PROPERTY_SOURCE_NAME_SEPARATOR));
return new SourceData(ConfigUtils.sourceName(target, names, namespace), data.data());
}
/**
* Implementation specific (fabric8 or k8s-native) way to get the data from then given
* source names.
* @param labels the ones that have been configured
* @param profiles profiles to taken into account when gathering source data. Can be
* empty.
* @return a container that holds the names of the source that were found and their
* data
*/
public abstract MultipleSourcesContainer dataSupplier(Map<String, String> labels, Set<String> profiles);
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2013-2022 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.commons.config;
import java.util.Map;
import java.util.Set;
/**
* @author wind57
*
* Container that stores multiple sources, to be exact their names and their flattenned
* data.
*/
public final record MultipleSourcesContainer(Set<String> names, Map<String, Object> data) {
private static final MultipleSourcesContainer EMPTY = new MultipleSourcesContainer(Set.of(), Map.of());
public static MultipleSourcesContainer empty() {
return EMPTY;
}
}

View File

@@ -27,14 +27,24 @@ public final class NamedSecretNormalizedSource extends NormalizedSource {
private final ConfigUtils.Prefix prefix;
public NamedSecretNormalizedSource(String name, String namespace, boolean failFast, ConfigUtils.Prefix prefix) {
private final boolean includeProfileSpecificSources;
public NamedSecretNormalizedSource(String name, String namespace, boolean failFast, ConfigUtils.Prefix prefix,
boolean includeProfileSpecificSources) {
super(name, namespace, failFast);
this.prefix = Objects.requireNonNull(prefix);
this.includeProfileSpecificSources = includeProfileSpecificSources;
}
public NamedSecretNormalizedSource(String name, String namespace, boolean failFast) {
public NamedSecretNormalizedSource(String name, String namespace, boolean failFast,
boolean includeProfileSpecificSources) {
super(name, namespace, failFast);
this.prefix = ConfigUtils.Prefix.DEFAULT;
this.includeProfileSpecificSources = includeProfileSpecificSources;
}
public boolean profileSpecificSources() {
return includeProfileSpecificSources;
}
public ConfigUtils.Prefix prefix() {

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2013-2022 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.commons.config;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static org.springframework.cloud.kubernetes.commons.config.ConfigUtils.onException;
import static org.springframework.cloud.kubernetes.commons.config.Constants.PROPERTY_SOURCE_NAME_SEPARATOR;
/**
* @author wind57
*
* Base class when dealing with named sources that support profiles specific sources,
* prefix based properties and single file yaml/properties.
*/
public abstract class NamedSourceData {
public final SourceData compute(String initialSourceName, ConfigUtils.Prefix prefix, String target,
boolean profileSources, boolean failFast, String namespace, String[] activeProfiles) {
Set<String> sourceNames = new HashSet<>();
sourceNames.add(initialSourceName);
MultipleSourcesContainer data = MultipleSourcesContainer.empty();
String currentSourceName;
try {
if (profileSources) {
for (String activeProfile : activeProfiles) {
currentSourceName = initialSourceName + "-" + activeProfile;
sourceNames.add(currentSourceName);
}
}
data = dataSupplier(sourceNames);
if (data.names().isEmpty()) {
return new SourceData(ConfigUtils.sourceName(target, initialSourceName, namespace), Map.of());
}
if (prefix != ConfigUtils.Prefix.DEFAULT) {
// since we are in a named source, calling get on the supplier is safe
String prefixToUse = prefix.prefixProvider().get();
PrefixContext prefixContext = new PrefixContext(data.data(), prefixToUse, namespace, data.names());
return ConfigUtils.withPrefix(target, prefixContext);
}
}
catch (Exception e) {
onException(failFast, e);
}
String names = data.names().stream().sorted().collect(Collectors.joining(PROPERTY_SOURCE_NAME_SEPARATOR));
return new SourceData(ConfigUtils.sourceName(target, names, namespace), data.data());
}
/**
* Implementation specific (fabric8 or k8s-native) way to get the data from then given
* source names.
* @param sourceNames the ones that have been configured
* @return an Entry that holds the names of the source that were found and their data
*/
public abstract MultipleSourcesContainer dataSupplier(Set<String> sourceNames);
}

View File

@@ -24,7 +24,7 @@ import java.util.Optional;
*
* @author wind57
*/
public sealed abstract class NormalizedSource permits NamedSecretNormalizedSource,LabeledSecretNormalizedSource,NamedConfigMapNormalizedSource {
public sealed abstract class NormalizedSource permits NamedSecretNormalizedSource,LabeledSecretNormalizedSource,NamedConfigMapNormalizedSource,LabeledConfigMapNormalizedSource {
private final String namespace;

View File

@@ -36,6 +36,11 @@ public enum NormalizedSourceType {
/**
* denotes the fact that this is a config map source based on name.
*/
NAMED_CONFIG_MAP
NAMED_CONFIG_MAP,
/**
* denotes the fact that this is a config map source based on labels.
*/
LABELED_CONFIG_MAP
}

View File

@@ -85,28 +85,26 @@ public class SecretsConfigProperties extends AbstractConfigProperties {
}
/**
* @return A list of Source to use. If the user has not specified any Source
* properties, then a single Source is constructed based on the supplied name and
* namespace.
*
* These are the actual name/namespace pairs that are used to create a
* SecretsPropertySource
* @return A list of Secret source(s) to use.
*/
public List<NormalizedSource> determineSources(Environment environment) {
if (this.sources.isEmpty()) {
List<NormalizedSource> result = new ArrayList<>(2);
String name = getApplicationName(environment, this.name, "Secret");
result.add(new NamedSecretNormalizedSource(name, this.namespace, this.failFast));
result.add(new NamedSecretNormalizedSource(name, this.namespace, this.failFast,
this.includeProfileSpecificSources));
if (!labels.isEmpty()) {
result.add(new LabeledSecretNormalizedSource(this.namespace, this.labels, this.failFast,
ConfigUtils.Prefix.DEFAULT));
ConfigUtils.Prefix.DEFAULT, false));
}
return result;
}
return this.sources.stream().flatMap(s -> s.normalize(this.name, this.namespace, this.labels, this.failFast,
this.useNameAsPrefix, environment)).collect(Collectors.toList());
return this.sources
.stream().flatMap(s -> s.normalize(this.name, this.namespace, this.labels,
this.includeProfileSpecificSources, this.failFast, this.useNameAsPrefix, environment))
.collect(Collectors.toList());
}
public static class Source {
@@ -137,6 +135,12 @@ public class SecretsConfigProperties extends AbstractConfigProperties {
*/
private Boolean useNameAsPrefix;
/**
* Use profile name to append to a config map name. Can't be a primitive, we need
* to know if it was explicitly set or not
*/
protected Boolean includeProfileSpecificSources;
public Source() {
}
@@ -180,13 +184,21 @@ public class SecretsConfigProperties extends AbstractConfigProperties {
this.useNameAsPrefix = useNameAsPrefix;
}
public Boolean getIncludeProfileSpecificSources() {
return includeProfileSpecificSources;
}
public void setIncludeProfileSpecificSources(Boolean includeProfileSpecificSources) {
this.includeProfileSpecificSources = includeProfileSpecificSources;
}
public boolean isEmpty() {
return !StringUtils.hasLength(this.name) && !StringUtils.hasLength(this.namespace);
}
private Stream<NormalizedSource> normalize(String defaultName, String defaultNamespace,
Map<String, String> defaultLabels, boolean failFast, boolean defaultUseNameAsPrefix,
Environment environment) {
Map<String, String> defaultLabels, boolean defaultIncludeProfileSpecificSources, boolean failFast,
boolean defaultUseNameAsPrefix, Environment environment) {
Stream.Builder<NormalizedSource> normalizedSources = Stream.builder();
@@ -199,13 +211,15 @@ public class SecretsConfigProperties extends AbstractConfigProperties {
ConfigUtils.Prefix prefix = ConfigUtils.findPrefix(this.explicitPrefix, this.useNameAsPrefix,
defaultUseNameAsPrefix, normalizedName);
boolean includeProfileSpecificSources = ConfigUtils.includeProfileSpecificSources(
defaultIncludeProfileSpecificSources, this.includeProfileSpecificSources);
NormalizedSource namedBasedSource = new NamedSecretNormalizedSource(secretName, normalizedNamespace,
failFast, prefix);
failFast, prefix, includeProfileSpecificSources);
normalizedSources.add(namedBasedSource);
if (!normalizedLabels.isEmpty()) {
NormalizedSource labeledBasedSource = new LabeledSecretNormalizedSource(normalizedNamespace, labels,
failFast, prefix);
failFast, prefix, includeProfileSpecificSources);
normalizedSources.add(labeledBasedSource);
}

View File

@@ -37,21 +37,22 @@ import static org.springframework.cloud.kubernetes.commons.config.PropertySource
import static org.springframework.cloud.kubernetes.commons.config.PropertySourceUtils.yamlParserGenerator;
/**
* A {@link MapPropertySource} that uses Kubernetes config maps.
* Processor that extracts data from an input, where input can be a single yaml/properties
* file.
*
* @author Ioannis Canellos
* @author Ali Shahbour
* @author Michael Moudatsos
*/
public abstract class ConfigMapPropertySource extends MapPropertySource {
public class SourceDataEntriesProcessor extends MapPropertySource {
private static final Log LOG = LogFactory.getLog(ConfigMapPropertySource.class);
private static final Log LOG = LogFactory.getLog(SourceDataEntriesProcessor.class);
public ConfigMapPropertySource(SourceData sourceData) {
public SourceDataEntriesProcessor(SourceData sourceData) {
super(sourceData.sourceName(), sourceData.sourceData());
}
protected static Map<String, Object> processAllEntries(Map<String, String> input, Environment environment) {
public static Map<String, Object> processAllEntries(Map<String, String> input, Environment environment) {
Set<Map.Entry<String, String>> entrySet = input.entrySet();
if (entrySet.size() == 1) {

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2013-2022 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.commons.config;
import java.util.Map;
/**
* @author wind57
*
* Container for some of the source's fields, it holds its labels (nullable), name and
* data.
*/
public final record StrippedSourceContainer(Map<String, String> labels, String name, Map<String, String> data) {
}

View File

@@ -18,7 +18,10 @@ package org.springframework.cloud.kubernetes.commons.config;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -336,4 +339,89 @@ class ConfigMapConfigPropertiesTests {
Assertions.assertFalse(((NamedConfigMapNormalizedSource) sources.get(2)).profileSpecificSources());
}
/**
* <pre>
* spring:
* cloud:
* kubernetes:
* config:
* useNameAsPrefix: false
* namespace: spring-k8s
* includeProfileSpecificSources: false
* sources:
* - labels:
* - name: first-label
* value: configmap-one
* useNameAsPrefix: false
* explicitPrefix: one
* - labels:
* - name: second-label
* value: configmap-two
* includeProfileSpecificSources: true
* useNameAsPrefix: true
* explicitPrefix: two
* - labels:
* - name: third-label
* value: configmap-three
* explicitPrefix: three
* - labels:
* - name: fourth-label
* value: configmap-four
* </pre>
*
*/
@Test
void testLabelsMultipleCases() {
ConfigMapConfigProperties properties = new ConfigMapConfigProperties();
properties.setUseNameAsPrefix(false);
properties.setNamespace("spring-k8s");
properties.setIncludeProfileSpecificSources(false);
ConfigMapConfigProperties.Source one = new ConfigMapConfigProperties.Source();
one.setLabels(Map.of("first-label", "configmap-one"));
one.setUseNameAsPrefix(false);
one.setExplicitPrefix("one");
ConfigMapConfigProperties.Source two = new ConfigMapConfigProperties.Source();
two.setLabels(Map.of("second-label", "configmap-two"));
two.setUseNameAsPrefix(true);
two.setExplicitPrefix("two");
two.setIncludeProfileSpecificSources(true);
ConfigMapConfigProperties.Source three = new ConfigMapConfigProperties.Source();
three.setLabels(Map.of("third-label", "configmap-three"));
three.setExplicitPrefix("three");
ConfigMapConfigProperties.Source four = new ConfigMapConfigProperties.Source();
four.setLabels(Map.of("fourth-label", "configmap-four"));
properties.setSources(Arrays.asList(one, two, three, four));
List<NormalizedSource> sources = properties.determineSources(new MockEnvironment());
// we get 8 property sources, since "named" ones with "application" are
// duplicated.
// that's OK, since later in the code we get a LinkedHashSet out of them all,
// so they become 5 only.
Assertions.assertEquals(sources.size(), 8, "4 NormalizedSources are expected");
LabeledConfigMapNormalizedSource labeled1 = (LabeledConfigMapNormalizedSource) sources.get(1);
Assertions.assertEquals(labeled1.prefix().prefixProvider().get(), "one");
Assertions.assertFalse(labeled1.profileSpecificSources());
LabeledConfigMapNormalizedSource labeled3 = (LabeledConfigMapNormalizedSource) sources.get(3);
Assertions.assertEquals(labeled3.prefix().prefixProvider().get(), "two");
Assertions.assertTrue(labeled3.profileSpecificSources());
LabeledConfigMapNormalizedSource labeled5 = (LabeledConfigMapNormalizedSource) sources.get(5);
Assertions.assertEquals(labeled5.prefix().prefixProvider().get(), "three");
Assertions.assertFalse(labeled5.profileSpecificSources());
LabeledConfigMapNormalizedSource labeled7 = (LabeledConfigMapNormalizedSource) sources.get(7);
Assertions.assertSame(labeled7.prefix(), ConfigUtils.Prefix.DEFAULT);
Assertions.assertFalse(labeled7.profileSpecificSources());
Set<NormalizedSource> set = new LinkedHashSet<>(sources);
Assertions.assertEquals(5, set.size());
}
}

View File

@@ -138,11 +138,22 @@ class ConfigUtilsTests {
SourceData result = ConfigUtils.withPrefix("configmap", context);
Assertions.assertEquals(result.sourceName().length(), 31);
Assertions.assertTrue(result.sourceName().contains("name2"));
Assertions.assertTrue(result.sourceName().contains("name1"));
Assertions.assertTrue(result.sourceName().contains("configmap"));
Assertions.assertTrue(result.sourceName().contains("namespace"));
Assertions.assertEquals(result.sourceName(), "configmap.name1.name2.namespace");
Assertions.assertEquals(result.sourceData().get("prefix.a"), "b");
Assertions.assertEquals(result.sourceData().get("prefix.c"), "d");
}
/*
* source names should be reproducible all the time, this test asserts this.
*/
@Test
void testWithPrefixSortedName() {
PrefixContext context = new PrefixContext(Map.of("a", "b", "c", "d"), "prefix", "namespace",
Set.of("namec", "namea", "nameb"));
SourceData result = ConfigUtils.withPrefix("configmap", context);
Assertions.assertEquals(result.sourceName(), "configmap.namea.nameb.namec.namespace");
Assertions.assertEquals(result.sourceData().get("prefix.a"), "b");
Assertions.assertEquals(result.sourceData().get("prefix.c"), "d");

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2013-2022 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.commons.config;
import java.util.Map;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/**
* @author wind57
*/
class LabeledConfigMapNormalizedSourceTests {
private static final ConfigUtils.Prefix PREFIX = ConfigUtils.findPrefix("prefix", false, false, "prefix");
@Test
void testEqualsAndHashCode() {
LabeledConfigMapNormalizedSource left = new LabeledConfigMapNormalizedSource("name", Map.of("key", "value"),
false, false);
LabeledConfigMapNormalizedSource right = new LabeledConfigMapNormalizedSource("name", Map.of("key", "value"),
true, false);
Assertions.assertEquals(left.hashCode(), right.hashCode());
Assertions.assertEquals(left, right);
}
@Test
void testType() {
LabeledConfigMapNormalizedSource source = new LabeledConfigMapNormalizedSource("name", Map.of("key", "value"),
false, false);
Assertions.assertSame(source.type(), NormalizedSourceType.LABELED_CONFIG_MAP);
}
@Test
void testTarget() {
LabeledConfigMapNormalizedSource source = new LabeledConfigMapNormalizedSource("name", Map.of("key", "value"),
false, false);
Assertions.assertEquals(source.target(), "configmap");
}
@Test
void testConstructorFields() {
LabeledConfigMapNormalizedSource source = new LabeledConfigMapNormalizedSource("namespace",
Map.of("key", "value"), false, PREFIX, true);
Assertions.assertEquals(source.labels(), Map.of("key", "value"));
Assertions.assertEquals(source.namespace().get(), "namespace");
Assertions.assertFalse(source.failFast());
Assertions.assertSame(PREFIX, source.prefix());
Assertions.assertTrue(source.profileSpecificSources());
}
@Test
void testConstructorWithoutPrefixFields() {
LabeledConfigMapNormalizedSource source = new LabeledConfigMapNormalizedSource("namespace",
Map.of("key", "value"), true, true);
Assertions.assertEquals(source.labels(), Map.of("key", "value"));
Assertions.assertEquals(source.namespace().get(), "namespace");
Assertions.assertTrue(source.failFast());
Assertions.assertSame(ConfigUtils.Prefix.DEFAULT, source.prefix());
Assertions.assertTrue(source.profileSpecificSources());
}
}

View File

@@ -31,8 +31,8 @@ class LabeledSecretNormalizedSourceTests {
@Test
void testEqualsAndHashCode() {
LabeledSecretNormalizedSource left = new LabeledSecretNormalizedSource("namespace", labels, false);
LabeledSecretNormalizedSource right = new LabeledSecretNormalizedSource("namespace", labels, true);
LabeledSecretNormalizedSource left = new LabeledSecretNormalizedSource("namespace", labels, false, false);
LabeledSecretNormalizedSource right = new LabeledSecretNormalizedSource("namespace", labels, true, false);
Assertions.assertEquals(left.hashCode(), right.hashCode());
Assertions.assertEquals(left, right);
@@ -48,8 +48,10 @@ class LabeledSecretNormalizedSourceTests {
ConfigUtils.Prefix knownLeft = ConfigUtils.findPrefix("left", false, false, "some");
ConfigUtils.Prefix knownRight = ConfigUtils.findPrefix("right", false, false, "some");
LabeledSecretNormalizedSource left = new LabeledSecretNormalizedSource("namespace", labels, true, knownLeft);
LabeledSecretNormalizedSource right = new LabeledSecretNormalizedSource("namespace", labels, true, knownRight);
LabeledSecretNormalizedSource left = new LabeledSecretNormalizedSource("namespace", labels, true, knownLeft,
false);
LabeledSecretNormalizedSource right = new LabeledSecretNormalizedSource("namespace", labels, true, knownRight,
false);
Assertions.assertEquals(left.hashCode(), right.hashCode());
Assertions.assertEquals(left, right);
@@ -57,30 +59,40 @@ class LabeledSecretNormalizedSourceTests {
@Test
void testType() {
LabeledSecretNormalizedSource source = new LabeledSecretNormalizedSource("namespace", labels, false);
LabeledSecretNormalizedSource source = new LabeledSecretNormalizedSource("namespace", labels, false, false);
Assertions.assertSame(source.type(), NormalizedSourceType.LABELED_SECRET);
}
@Test
void testImmutableGetLabels() {
LabeledSecretNormalizedSource source = new LabeledSecretNormalizedSource("namespace", labels, false);
LabeledSecretNormalizedSource source = new LabeledSecretNormalizedSource("namespace", labels, false, false);
Assertions.assertThrows(RuntimeException.class, () -> source.labels().put("c", "d"));
}
@Test
void testTarget() {
LabeledSecretNormalizedSource source = new LabeledSecretNormalizedSource("namespace", labels, false);
LabeledSecretNormalizedSource source = new LabeledSecretNormalizedSource("namespace", labels, false, false);
Assertions.assertEquals(source.target(), "secret");
}
@Test
void testConstructorFields() {
ConfigUtils.Prefix prefix = ConfigUtils.findPrefix("prefix", false, false, "some");
LabeledSecretNormalizedSource source = new LabeledSecretNormalizedSource("namespace", labels, false, prefix);
LabeledSecretNormalizedSource source = new LabeledSecretNormalizedSource("namespace", labels, false, prefix,
true);
Assertions.assertTrue(source.name().isEmpty());
Assertions.assertEquals(source.namespace().get(), "namespace");
Assertions.assertFalse(source.failFast());
Assertions.assertSame(source.prefix(), prefix);
Assertions.assertTrue(source.profileSpecificSources());
}
@Test
void testConstructorWithoutPrefixFields() {
LabeledSecretNormalizedSource source = new LabeledSecretNormalizedSource("namespace", labels, true, true);
Assertions.assertEquals(source.namespace().get(), "namespace");
Assertions.assertTrue(source.failFast());
Assertions.assertSame(ConfigUtils.Prefix.DEFAULT, source.prefix());
Assertions.assertTrue(source.profileSpecificSources());
}
}

View File

@@ -28,8 +28,8 @@ class NamedSecretNormalizedSourceTests {
@Test
void testEqualsAndHashCode() {
NamedSecretNormalizedSource left = new NamedSecretNormalizedSource("name", "namespace", false);
NamedSecretNormalizedSource right = new NamedSecretNormalizedSource("name", "namespace", true);
NamedSecretNormalizedSource left = new NamedSecretNormalizedSource("name", "namespace", false, false);
NamedSecretNormalizedSource right = new NamedSecretNormalizedSource("name", "namespace", true, false);
Assertions.assertEquals(left.hashCode(), right.hashCode());
Assertions.assertEquals(left, right);
@@ -37,23 +37,34 @@ class NamedSecretNormalizedSourceTests {
@Test
void testType() {
NamedSecretNormalizedSource source = new NamedSecretNormalizedSource("name", "namespace", false);
NamedSecretNormalizedSource source = new NamedSecretNormalizedSource("name", "namespace", false, false);
Assertions.assertSame(source.type(), NormalizedSourceType.NAMED_SECRET);
}
@Test
void testTarget() {
NamedSecretNormalizedSource source = new NamedSecretNormalizedSource("name", "namespace", false);
NamedSecretNormalizedSource source = new NamedSecretNormalizedSource("name", "namespace", false, false);
Assertions.assertEquals(source.target(), "secret");
}
@Test
void testConstructorFields() {
NamedSecretNormalizedSource source = new NamedSecretNormalizedSource("name", "namespace", false, PREFIX);
NamedSecretNormalizedSource source = new NamedSecretNormalizedSource("name", "namespace", false, PREFIX, true);
Assertions.assertEquals(source.name().get(), "name");
Assertions.assertEquals(source.namespace().get(), "namespace");
Assertions.assertFalse(source.failFast());
Assertions.assertSame(PREFIX, source.prefix());
Assertions.assertTrue(source.profileSpecificSources());
}
@Test
void testConstructorWithoutPrefixFields() {
NamedSecretNormalizedSource source = new NamedSecretNormalizedSource("name", "namespace", true, true);
Assertions.assertEquals(source.name().get(), "name");
Assertions.assertEquals(source.namespace().get(), "namespace");
Assertions.assertTrue(source.failFast());
Assertions.assertSame(ConfigUtils.Prefix.DEFAULT, source.prefix());
Assertions.assertTrue(source.profileSpecificSources());
}
}

View File

@@ -313,6 +313,7 @@ class SecretsConfigPropertiesTests {
* secrets:
* useNameAsPrefix: false
* namespace: spring-k8s
* includeProfileSpecificSources: false
* sources:
* - labels:
* - name: first-label
@@ -322,6 +323,7 @@ class SecretsConfigPropertiesTests {
* - labels:
* - name: second-label
* value: secret-two
* includeProfileSpecificSources: true
* useNameAsPrefix: true
* explicitPrefix: two
* - labels:
@@ -339,6 +341,7 @@ class SecretsConfigPropertiesTests {
SecretsConfigProperties properties = new SecretsConfigProperties();
properties.setUseNameAsPrefix(false);
properties.setNamespace("spring-k8s");
properties.setIncludeProfileSpecificSources(false);
SecretsConfigProperties.Source one = new SecretsConfigProperties.Source();
one.setLabels(Map.of("first-label", "secret-one"));
@@ -349,6 +352,7 @@ class SecretsConfigPropertiesTests {
two.setLabels(Map.of("second-label", "secret-two"));
two.setUseNameAsPrefix(true);
two.setExplicitPrefix("two");
two.setIncludeProfileSpecificSources(true);
SecretsConfigProperties.Source three = new SecretsConfigProperties.Source();
three.setLabels(Map.of("third-label", "secret-three"));
@@ -366,13 +370,21 @@ class SecretsConfigPropertiesTests {
// so they become 5 only.
Assertions.assertEquals(sources.size(), 8, "4 NormalizedSources are expected");
Assertions.assertEquals(((LabeledSecretNormalizedSource) sources.get(1)).prefix().prefixProvider().get(),
"one");
Assertions.assertEquals(((LabeledSecretNormalizedSource) sources.get(3)).prefix().prefixProvider().get(),
"two");
Assertions.assertEquals(((LabeledSecretNormalizedSource) sources.get(5)).prefix().prefixProvider().get(),
"three");
Assertions.assertSame(((LabeledSecretNormalizedSource) sources.get(7)).prefix(), ConfigUtils.Prefix.DEFAULT);
LabeledSecretNormalizedSource labeled1 = (LabeledSecretNormalizedSource) sources.get(1);
Assertions.assertEquals(labeled1.prefix().prefixProvider().get(), "one");
Assertions.assertFalse(labeled1.profileSpecificSources());
LabeledSecretNormalizedSource labeled3 = (LabeledSecretNormalizedSource) sources.get(3);
Assertions.assertEquals(labeled3.prefix().prefixProvider().get(), "two");
Assertions.assertTrue(labeled3.profileSpecificSources());
LabeledSecretNormalizedSource labeled5 = (LabeledSecretNormalizedSource) sources.get(5);
Assertions.assertEquals(labeled5.prefix().prefixProvider().get(), "three");
Assertions.assertFalse(labeled5.profileSpecificSources());
LabeledSecretNormalizedSource labeled7 = (LabeledSecretNormalizedSource) sources.get(7);
Assertions.assertSame(labeled7.prefix(), ConfigUtils.Prefix.DEFAULT);
Assertions.assertFalse(labeled7.profileSpecificSources());
Set<NormalizedSource> set = new LinkedHashSet<>(sources);
Assertions.assertEquals(5, set.size());

View File

@@ -96,7 +96,7 @@ public class KubernetesConfigServerAutoConfiguration {
List<MapPropertySource> propertySources = new ArrayList<>();
namespaces.forEach(space -> {
NormalizedSource source = new NamedSecretNormalizedSource(applicationName, space, false);
NormalizedSource source = new NamedSecretNormalizedSource(applicationName, space, false, false);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(coreApi, source, space,
springEnv);
propertySources.add(new KubernetesClientSecretsPropertySource(context));

Some files were not shown because too many files have changed in this diff Show More