labels, profiles, prefix, single yaml/properties for secrets and config maps (#1026)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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())));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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(","));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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")));
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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))));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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))));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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))));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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))));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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))));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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))));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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))));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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))));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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")
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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))));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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)));
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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)));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 -> {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
/**
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
@@ -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) {
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user