Labeled secrets prefix support (#1017)

This commit is contained in:
erabii
2022-05-26 16:02:57 +03:00
committed by GitHub
parent 84cb46a576
commit df5066b9e5
58 changed files with 1763 additions and 154 deletions

View File

@@ -409,6 +409,71 @@ will result in three properties being generated:
- `config-map-three.greetings.message` equal to `Say Hello from three`.
The same way you configure a prefix for configmaps, you can do it for secrets also; both for secrets that are based on name
and the ones based on labels. For example:
====
[source.yaml]
----
spring:
application:
name: prefix-based-secrets
cloud:
kubernetes:
secrets:
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
- name: my-secret
----
====
The same processing rules apply when generating property source as for config maps. The only difference is that
potentially, looking up secrets by labels can mean that we find more than one source. In such a case, prefix (if specified via `useNameAsPrefix`)
will be the names of all secrets found for those particular labels.
One more thing to bear in mind is that we support `prefix` per _source_, not per secret. The easiest way to explain this is via an example:
====
[source.yaml]
----
spring:
application:
name: prefix-based-secrets
cloud:
kubernetes:
secrets:
enableApi: true
useNameAsPrefix: true
namespace: spring-k8s
sources:
- labels:
color: blue
useNameAsPrefix: true
----
====
Suppose that a query matching such a label will provide two secrets as a result: `secret-a` and `secret-b`.
Both of these secrets have the same property name: `color=sea-blue` and `color=ocean-blue`. It is undefined which
`color` will end-up as part of property sources, but the prefix for it will be `secret-a.secret-b`
(concatenated sorted naturally, names of the secrets).
If you need more fine-grained results, adding more labels to identify the secret uniquely would be an option.
By default, besides reading the config map that is specified in the `sources` configuration, Spring will also try to read
all properties from "profile aware" sources. The easiest way to explain this is via an example. Let's suppose your application
enables a profile called "dev" and you have a configuration like the one below:

View File

@@ -17,8 +17,10 @@
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;
@@ -29,6 +31,7 @@ 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;
@@ -66,9 +69,10 @@ final class LabeledSecretContextToSourceDataProvider implements Supplier<Kuberne
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 sourceName = String.join(PROPERTY_SOURCE_NAME_SEPARATOR, labels.keySet());
String sourceNameFromLabels = String.join(PROPERTY_SOURCE_NAME_SEPARATOR, labels.keySet());
try {
@@ -77,10 +81,42 @@ final class LabeledSecretContextToSourceDataProvider implements Supplier<Kuberne
createLabelsSelector(labels), null, null, null, null, null).getItems();
if (!secrets.isEmpty()) {
sourceName = secrets.stream().map(V1Secret::getMetadata).map(V1ObjectMeta::getName)
.collect(Collectors.joining(PROPERTY_SOURCE_NAME_SEPARATOR));
secrets.forEach(s -> result.putAll(dataFromSecret(s, namespace)));
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);
}
}
@@ -89,8 +125,12 @@ final class LabeledSecretContextToSourceDataProvider implements Supplier<Kuberne
onException(source.failFast(), message, e);
}
String propertySourceName = ConfigUtils.sourceName(source.target(), sourceName, namespace);
return new SourceData(propertySourceName, result);
// 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);
};
}

View File

@@ -104,9 +104,10 @@ final class NamedConfigMapContextToSourceDataProvider implements Supplier<Kubern
propertySourceNames.add(entry.getValue());
});
if (!"".equals(source.prefix())) {
PrefixContext prefixContext = new PrefixContext(result, source.prefix(), namespace,
propertySourceNames);
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);
}

View File

@@ -72,9 +72,10 @@ final class NamedSecretContextToSourceDataProvider implements Supplier<Kubernete
secret.ifPresent(s -> result.putAll(dataFromSecret(s, namespace)));
if (!"".equals(source.prefix()) && !result.isEmpty()) {
PrefixContext prefixContext = new PrefixContext(result, source.prefix(), namespace,
propertySourceNames);
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);
}

View File

@@ -31,6 +31,7 @@ import org.junit.jupiter.api.AfterEach;
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.NamedConfigMapNormalizedSource;
import org.springframework.cloud.kubernetes.commons.config.NormalizedSource;
import org.springframework.mock.env.MockEnvironment;
@@ -100,7 +101,7 @@ class KubernetesClientConfigMapPropertySourceTests {
stubFor(get("/api/v1/namespaces/default/configmaps")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(PROPERTIES_CONFIGMAP_LIST))));
NormalizedSource source = new NamedConfigMapNormalizedSource("bootstrap-640", "default", false, "", true);
NormalizedSource source = new NamedConfigMapNormalizedSource("bootstrap-640", "default", false, true);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, "default",
new MockEnvironment());
KubernetesClientConfigMapPropertySource propertySource = new KubernetesClientConfigMapPropertySource(context);
@@ -121,7 +122,7 @@ class KubernetesClientConfigMapPropertySourceTests {
stubFor(get("/api/v1/namespaces/default/configmaps")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(YAML_CONFIGMAP_LIST))));
NormalizedSource source = new NamedConfigMapNormalizedSource("bootstrap-641", "default", false, "", true);
NormalizedSource source = new NamedConfigMapNormalizedSource("bootstrap-641", "default", false, true);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, "default",
new MockEnvironment());
KubernetesClientConfigMapPropertySource propertySource = new KubernetesClientConfigMapPropertySource(context);
@@ -142,7 +143,8 @@ class KubernetesClientConfigMapPropertySourceTests {
stubFor(get("/api/v1/namespaces/default/configmaps")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(PROPERTIES_CONFIGMAP_LIST))));
NormalizedSource source = new NamedConfigMapNormalizedSource("bootstrap-640", "default", false, "prefix", true);
ConfigUtils.Prefix prefix = ConfigUtils.findPrefix("prefix", false, false, null);
NormalizedSource source = new NamedConfigMapNormalizedSource("bootstrap-640", "default", false, prefix, true);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, "default",
new MockEnvironment());
KubernetesClientConfigMapPropertySource propertySource = new KubernetesClientConfigMapPropertySource(context);
@@ -161,7 +163,8 @@ class KubernetesClientConfigMapPropertySourceTests {
@Test
void constructorWithNamespaceMustNotFail() {
NormalizedSource source = new NamedConfigMapNormalizedSource("bootstrap-640", "default", false, "prefix", true);
ConfigUtils.Prefix prefix = ConfigUtils.findPrefix("prefix", false, false, null);
NormalizedSource source = new NamedConfigMapNormalizedSource("bootstrap-640", "default", false, prefix, true);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(new CoreV1Api(), source, "default",
new MockEnvironment());
@@ -173,7 +176,8 @@ class KubernetesClientConfigMapPropertySourceTests {
stubFor(get("/api/v1/namespaces/default/configmaps")
.willReturn(aResponse().withStatus(500).withBody("Internal Server Error")));
NormalizedSource source = new NamedConfigMapNormalizedSource("my-config", "default", true, "prefix", true);
ConfigUtils.Prefix prefix = ConfigUtils.findPrefix("prefix", false, false, null);
NormalizedSource source = new NamedConfigMapNormalizedSource("my-config", "default", true, prefix, true);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(new CoreV1Api(), source, "default",
new MockEnvironment());
@@ -188,7 +192,8 @@ class KubernetesClientConfigMapPropertySourceTests {
stubFor(get("/api/v1/namespaces/default/configmaps")
.willReturn(aResponse().withStatus(500).withBody("Internal Server Error")));
NormalizedSource source = new NamedConfigMapNormalizedSource("my-config", "default", false, "prefix", true);
ConfigUtils.Prefix prefix = ConfigUtils.findPrefix("prefix", false, false, null);
NormalizedSource source = new NamedConfigMapNormalizedSource("my-config", "default", false, prefix, true);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(new CoreV1Api(), source, "default",
new MockEnvironment());

View File

@@ -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);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, "default",
new MockEnvironment());
@@ -151,7 +151,7 @@ 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);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, "default",
new MockEnvironment());
@@ -166,7 +166,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);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, "default",
new MockEnvironment());

View File

@@ -18,6 +18,7 @@ package org.springframework.cloud.kubernetes.client.config;
import java.util.Base64;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -36,6 +37,7 @@ 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.LabeledSecretNormalizedSource;
import org.springframework.cloud.kubernetes.commons.config.NormalizedSource;
import org.springframework.cloud.kubernetes.commons.config.SourceData;
@@ -193,4 +195,93 @@ class LabeledSecretContextToSourceDataProviderTests {
Assertions.assertEquals(sourceData.sourceData(), Map.of("color", "really-red"));
}
/**
* one secret with name : "blue-secret" 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() {
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());
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);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());
KubernetesClientContextToSourceData data = new LabeledSecretContextToSourceDataProvider().get();
SourceData sourceData = data.apply(context);
Assertions.assertEquals("secret.blue-secret.default", sourceData.sourceName());
Assertions.assertEquals(Map.of("me.what-color", "blue-color"), sourceData.sourceData());
}
/**
* two secrets are deployed (name:blue-secret, name:another-blue-secret) 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 secret names.
*
*/
@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());
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);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());
KubernetesClientContextToSourceData data = new LabeledSecretContextToSourceDataProvider().get();
SourceData sourceData = data.apply(context);
// maps don't have a defined order, so assert components separately
Assertions.assertEquals(46, sourceData.sourceName().length());
Assertions.assertTrue(sourceData.sourceName().contains("secret"));
Assertions.assertTrue(sourceData.sourceName().contains("blue-secret"));
Assertions.assertTrue(sourceData.sourceName().contains("another-blue-secret"));
Assertions.assertTrue(sourceData.sourceName().contains("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-secret.blue-secret.first");
}
Assertions.assertEquals(secondKey, "another-blue-secret.blue-secret.second");
Assertions.assertEquals(properties.get(firstKey), "blue");
Assertions.assertEquals(properties.get(secondKey), "blue");
}
}

View File

@@ -35,6 +35,7 @@ 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;
@@ -90,7 +91,7 @@ class NamedConfigMapContextToSourceDataProviderTests {
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);
NormalizedSource source = new NamedConfigMapNormalizedSource(BLUE_CONFIG_MAP_NAME, NAMESPACE, true, false);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());
@@ -119,7 +120,7 @@ class NamedConfigMapContextToSourceDataProviderTests {
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);
NormalizedSource source = new NamedConfigMapNormalizedSource(RED_CONFIG_MAP_NAME, NAMESPACE, true, false);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());
@@ -154,7 +155,7 @@ class NamedConfigMapContextToSourceDataProviderTests {
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);
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);
@@ -194,7 +195,8 @@ class NamedConfigMapContextToSourceDataProviderTests {
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, "some",
ConfigUtils.Prefix prefix = ConfigUtils.findPrefix("some", false, false, null);
NormalizedSource source = new NamedConfigMapNormalizedSource(RED_CONFIG_MAP_NAME, NAMESPACE, true, prefix,
true);
MockEnvironment environment = new MockEnvironment();
environment.setActiveProfiles("with-profile");
@@ -241,7 +243,8 @@ class NamedConfigMapContextToSourceDataProviderTests {
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, "some",
ConfigUtils.Prefix prefix = ConfigUtils.findPrefix("some", false, false, null);
NormalizedSource source = new NamedConfigMapNormalizedSource(RED_CONFIG_MAP_NAME, NAMESPACE, true, prefix,
true);
MockEnvironment environment = new MockEnvironment();
environment.setActiveProfiles("with-taste", "with-shape");
@@ -271,7 +274,8 @@ class NamedConfigMapContextToSourceDataProviderTests {
CoreV1Api api = new CoreV1Api();
stubFor(get("/api/v1/namespaces/default/configmaps")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(configMapList))));
NormalizedSource source = new NamedConfigMapNormalizedSource("application", NAMESPACE, true, "some", false);
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());
@@ -302,7 +306,7 @@ class NamedConfigMapContextToSourceDataProviderTests {
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, "",
NormalizedSource source = new NamedConfigMapNormalizedSource(RED_CONFIG_MAP_NAME, NAMESPACE + "nope", true,
false);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());

View File

@@ -84,7 +84,7 @@ class NamedSecretContextToSourceDataProviderTests {
.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);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());
@@ -125,7 +125,7 @@ class NamedSecretContextToSourceDataProviderTests {
.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);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());
@@ -156,7 +156,7 @@ class NamedSecretContextToSourceDataProviderTests {
.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);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());
@@ -182,7 +182,7 @@ class NamedSecretContextToSourceDataProviderTests {
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(secretList))));
// blue does not match red
NormalizedSource source = new NamedSecretNormalizedSource("red", NAMESPACE + "nope", false, "");
NormalizedSource source = new NamedSecretNormalizedSource("red", NAMESPACE + "nope", false);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE,
new MockEnvironment());

View File

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

View File

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

View File

@@ -0,0 +1,61 @@
/*
* 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_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.LabeledSecretWithPrefixConfigurationStub.stubData;
/**
* @author wind57
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = LabeledSecretWithPrefixApp.class,
properties = { "spring.cloud.application.name=labeled-secret-with-prefix",
"labeled.secret.with.prefix.stub=true", "spring.main.cloud-platform=KUBERNETES",
"spring.config.import=kubernetes:,classpath:./labeled-secret-with-prefix.yaml",
"spring.cloud.kubernetes.client.namespace=spring-k8s" })
class LabeledSecretWithPrefixConfigDataTests extends LabeledSecretWithPrefixTests {
private static MockedStatic<KubernetesClientUtils> clientUtilsMock;
@BeforeAll
static void wireMock() {
WireMockServer server = new WireMockServer(options().dynamicPort());
server.start();
WireMock.configureFor("localhost", server.port());
clientUtilsMock = mockStatic(KubernetesClientUtils.class);
clientUtilsMock.when(KubernetesClientUtils::kubernetesApiClient)
.thenReturn(new ClientBuilder().setBasePath(server.baseUrl()).build());
stubData();
}
@AfterAll
static void teardown() {
clientUtilsMock.close();
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright 2013-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.labeled_secret_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.cloud.kubernetes.client.config.boostrap.stubs.LabeledSecretWithPrefixConfigurationStub;
import org.springframework.test.web.reactive.server.WebTestClient;
/**
* The stub data for this test is in : {@link LabeledSecretWithPrefixConfigurationStub}
*
* @author wind57
*/
abstract class LabeledSecretWithPrefixTests {
@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'
* ("one.property", "one")
*
* As such: @ConfigurationProperties("one")
* </pre>
*/
@Test
void testOne() {
this.webClient.get().uri("/labeled-secret/prefix/one").exchange().expectStatus().isOk().expectBody(String.class)
.value(Matchers.equalTo("one"));
}
/**
* <pre>
* 'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
* 'spring.cloud.kubernetes.secrets.sources[1].explicitPrefix=two'
* ("property", "two")
*
* As such: @ConfigurationProperties("two")
* </pre>
*/
@Test
void testTwo() {
this.webClient.get().uri("/labeled-secret/prefix/two").exchange().expectStatus().isOk().expectBody(String.class)
.value(Matchers.equalTo("two"));
}
/**
* <pre>
* 'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
* 'spring.cloud.kubernetes.secrets.sources[2].labels=letter:c'
* ("property", "three")
*
* We find the secret by labels, and use it's name as the prefix.
*
* As such: @ConfigurationProperties(prefix = "secret-three")
* </pre>
*/
@Test
void testThree() {
this.webClient.get().uri("/labeled-secret/prefix/three").exchange().expectStatus().isOk()
.expectBody(String.class).value(Matchers.equalTo("three"));
}
/**
* <pre>
* 'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
* 'spring.cloud.kubernetes.secrets.sources[3].labels=letter:d'
* ("property", "four")
*
* We find the secret by labels, and use it's name as the prefix.
*
* As such: @ConfigurationProperties(prefix = "secret-four")
* </pre>
*/
@Test
void testFour() {
this.webClient.get().uri("/labeled-secret/prefix/four").exchange().expectStatus().isOk()
.expectBody(String.class).value(Matchers.equalTo("four"));
}
}

View File

@@ -0,0 +1,64 @@
/*
* 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.labeled_secret_with_prefix.controller;
import org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_prefix.properties.Four;
import org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_prefix.properties.One;
import org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_prefix.properties.Three;
import org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_prefix.properties.Two;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LabeledSecretWithPrefixController {
private final One one;
private final Two two;
private final Three three;
private final Four four;
public LabeledSecretWithPrefixController(One one, Two two, Three three, Four four) {
this.one = one;
this.two = two;
this.three = three;
this.four = four;
}
@GetMapping("/labeled-secret/prefix/one")
public String one() {
return one.getProperty();
}
@GetMapping("/labeled-secret/prefix/two")
public String two() {
return two.getProperty();
}
@GetMapping("/labeled-secret/prefix/three")
public String three() {
return three.getProperty();
}
@GetMapping("/labeled-secret/prefix/four")
public String four() {
return four.getProperty();
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -23,7 +23,8 @@ import org.springframework.boot.test.context.SpringBootTest;
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = NamedSecretWithPrefixApp.class,
properties = { "spring.cloud.bootstrap.name=named-secret-with-prefix", "named.secret.with.prefix.stub=true",
"spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true" })
"spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
"spring.cloud.kubernetes.client.namespace=spring-k8s" })
class NamedSecretWithPrefixBootstrapTests extends NamedSecretWithPrefixTests {
}

View File

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

View File

@@ -0,0 +1,110 @@
/*
* 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.prefix.stub")
public class LabeledSecretWithPrefixConfigurationStub {
@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() {
V1Secret one = new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("secret-one").withNamespace("spring-k8s")
.withLabels(Map.of("letter", "a")).withResourceVersion("1").build())
.addToData(Collections.singletonMap("one.property", "one".getBytes())).build();
V1Secret two = new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("secret-two").withNamespace("spring-k8s")
.withLabels(Map.of("letter", "b")).withResourceVersion("1").build())
.addToData(Collections.singletonMap("property", "two".getBytes())).build();
V1Secret three = new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("secret-three").withNamespace("spring-k8s")
.withLabels(Map.of("letter", "c")).withResourceVersion("1").build())
.addToData(Collections.singletonMap("property", "three".getBytes())).build();
V1Secret four = new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("secret-four").withNamespace("spring-k8s")
.withLabels(Map.of("letter", "d")).withResourceVersion("1").build())
.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 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))));
}
}

View File

@@ -1,5 +1,6 @@
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.kubernetes.client.config.boostrap.stubs.IncludeProfileSpecificSourcesConfigurationStub, \
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.LabeledSecretWithPrefixConfigurationStub, \
org.springframework.cloud.kubernetes.client.config.boostrap.stubs.NamedSecretWithPrefixConfigurationStub, \
org.springframework.cloud.kubernetes.client.config.EnableRetryBootstrapConfiguration

View File

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

View File

@@ -93,7 +93,7 @@ public class ConfigMapConfigProperties extends AbstractConfigProperties {
}
String name = getApplicationName(environment, this.name, "Config Map");
return Collections.singletonList(
new NamedConfigMapNormalizedSource(name, namespace, failFast, "", includeProfileSpecificSources));
new NamedConfigMapNormalizedSource(name, namespace, failFast, includeProfileSpecificSources));
}
return sources.stream()
@@ -185,8 +185,8 @@ public class ConfigMapConfigProperties extends AbstractConfigProperties {
boolean defaultIncludeProfileSpecificSources, boolean failFast) {
String normalizedName = StringUtils.hasLength(this.name) ? this.name : defaultName;
String normalizedNamespace = StringUtils.hasLength(this.namespace) ? this.namespace : defaultNamespace;
String prefix = ConfigUtils.findPrefix(this.explicitPrefix, useNameAsPrefix, defaultUseNameAsPrefix,
normalizedName);
ConfigUtils.Prefix prefix = ConfigUtils.findPrefix(this.explicitPrefix, useNameAsPrefix,
defaultUseNameAsPrefix, normalizedName);
boolean includeProfileSpecificSources = ConfigUtils.includeProfileSpecificSources(
defaultIncludeProfileSpecificSources, this.includeProfileSpecificSources);
return new NamedConfigMapNormalizedSource(normalizedName, normalizedNamespace, failFast, prefix,

View File

@@ -17,6 +17,7 @@
package org.springframework.cloud.kubernetes.commons.config;
import java.util.Map;
import java.util.function.Supplier;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -61,31 +62,49 @@ public final class ConfigUtils {
* @param normalizedName either the name of
* 'spring.cloud.kubernetes.config|secrets.sources.name' or
* 'spring.cloud.kubernetes.config|secrets.name'
* @return prefix to use in normalized sources, never null
* @return prefix to use in normalized sources
*/
public static String findPrefix(String explicitPrefix, Boolean useNameAsPrefix, boolean defaultUseNameAsPrefix,
public static Prefix findPrefix(String explicitPrefix, Boolean useNameAsPrefix, boolean defaultUseNameAsPrefix,
String normalizedName) {
// if explicitPrefix is set, it takes priority over useNameAsPrefix
// (either the one from 'spring.cloud.kubernetes.config|secrets' or
// 'spring.cloud.kubernetes.config|secrets.sources')
if (StringUtils.hasText(explicitPrefix)) {
return explicitPrefix;
Prefix.computeKnown(() -> explicitPrefix);
return Prefix.KNOWN;
}
// useNameAsPrefix is a java.lang.Boolean and if it's != null, users have
// specified it explicitly
if (useNameAsPrefix != null) {
if (useNameAsPrefix) {
return normalizedName;
// this is the case when the source is searched by labels,
// but prefix support is enabled.
// in such cases, the name is not known yet.
if (normalizedName == null) {
return Prefix.DELAYED;
}
Prefix.computeKnown(() -> normalizedName);
return Prefix.KNOWN;
}
return "";
return Prefix.DEFAULT;
}
if (defaultUseNameAsPrefix) {
return normalizedName;
// this is the case when the source is searched by labels,
// but prefix support is enabled.ConfigUtilsTests
// in such cases, the name is not known yet.
if (normalizedName == null) {
return Prefix.DELAYED;
}
Prefix.computeKnown(() -> normalizedName);
return Prefix.KNOWN;
}
return "";
return Prefix.DEFAULT;
}
/**
@@ -130,4 +149,41 @@ public final class ConfigUtils {
return target + PROPERTY_SOURCE_NAME_SEPARATOR + applicationName + PROPERTY_SOURCE_NAME_SEPARATOR + namespace;
}
public static final class Prefix {
/**
* prefix has not been provided.
*/
public static final Prefix DEFAULT = new Prefix(() -> "");
/**
* prefix has been enabled, but the actual value will be known later; the value
* for the prefix will be the name of the source. (this is the case for a
* prefix-enabled labeled source for example)
*/
public static final Prefix DELAYED = new Prefix(() -> {
throw new IllegalArgumentException("prefix is delayed, needs to be taken elsewhere");
});
/**
* prefix is known at the callsite.
*/
public static Prefix KNOWN;
public Supplier<String> prefixProvider() {
return prefixProvider;
}
private final Supplier<String> prefixProvider;
private Prefix(Supplier<String> prefixProvider) {
this.prefixProvider = prefixProvider;
}
private static void computeKnown(Supplier<String> supplier) {
KNOWN = new Prefix(supplier);
}
}
}

View File

@@ -29,9 +29,19 @@ public final class LabeledSecretNormalizedSource extends NormalizedSource {
private final Map<String, String> labels;
private final ConfigUtils.Prefix prefix;
public LabeledSecretNormalizedSource(String namespace, Map<String, String> labels, boolean failFast,
ConfigUtils.Prefix prefix) {
super(null, namespace, failFast);
this.labels = Collections.unmodifiableMap(Objects.requireNonNull(labels));
this.prefix = Objects.requireNonNull(prefix);
}
public LabeledSecretNormalizedSource(String namespace, Map<String, String> labels, boolean failFast) {
super(null, namespace, failFast);
this.labels = Collections.unmodifiableMap(Objects.requireNonNull(labels));
this.prefix = ConfigUtils.Prefix.DEFAULT;
}
/**
@@ -41,6 +51,10 @@ public final class LabeledSecretNormalizedSource extends NormalizedSource {
return labels;
}
public ConfigUtils.Prefix prefix() {
return prefix;
}
@Override
public NormalizedSourceType type() {
return NormalizedSourceType.LABELED_SECRET;

View File

@@ -25,18 +25,25 @@ import java.util.Objects;
*/
public final class NamedConfigMapNormalizedSource extends NormalizedSource {
private final String prefix;
private final ConfigUtils.Prefix prefix;
private final boolean includeProfileSpecificSources;
public NamedConfigMapNormalizedSource(String name, String namespace, boolean failFast, String prefix,
public NamedConfigMapNormalizedSource(String name, String namespace, boolean failFast, ConfigUtils.Prefix prefix,
boolean includeProfileSpecificSources) {
super(name, namespace, failFast);
this.prefix = Objects.requireNonNull(prefix);
this.includeProfileSpecificSources = includeProfileSpecificSources;
}
public String prefix() {
public NamedConfigMapNormalizedSource(String name, String namespace, boolean failFast,
boolean includeProfileSpecificSources) {
super(name, namespace, failFast);
this.prefix = ConfigUtils.Prefix.DEFAULT;
this.includeProfileSpecificSources = includeProfileSpecificSources;
}
public ConfigUtils.Prefix prefix() {
return prefix;
}

View File

@@ -25,14 +25,19 @@ import java.util.Objects;
*/
public final class NamedSecretNormalizedSource extends NormalizedSource {
private final String prefix;
private final ConfigUtils.Prefix prefix;
public NamedSecretNormalizedSource(String name, String namespace, boolean failFast, String prefix) {
public NamedSecretNormalizedSource(String name, String namespace, boolean failFast, ConfigUtils.Prefix prefix) {
super(name, namespace, failFast);
this.prefix = prefix;
this.prefix = Objects.requireNonNull(prefix);
}
public String prefix() {
public NamedSecretNormalizedSource(String name, String namespace, boolean failFast) {
super(name, namespace, failFast);
this.prefix = ConfigUtils.Prefix.DEFAULT;
}
public ConfigUtils.Prefix prefix() {
return prefix;
}

View File

@@ -96,10 +96,11 @@ public class SecretsConfigProperties extends AbstractConfigProperties {
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.isFailFast(), ""));
result.add(new NamedSecretNormalizedSource(name, this.namespace, this.failFast));
if (!labels.isEmpty()) {
result.add(new LabeledSecretNormalizedSource(this.namespace, this.labels, this.isFailFast()));
result.add(new LabeledSecretNormalizedSource(this.namespace, this.labels, this.failFast,
ConfigUtils.Prefix.DEFAULT));
}
return result;
}
@@ -195,17 +196,17 @@ public class SecretsConfigProperties extends AbstractConfigProperties {
String secretName = getApplicationName(environment, normalizedName, "Secret");
String prefix = ConfigUtils.findPrefix(this.explicitPrefix, this.useNameAsPrefix, defaultUseNameAsPrefix,
normalizedName);
ConfigUtils.Prefix prefix = ConfigUtils.findPrefix(this.explicitPrefix, this.useNameAsPrefix,
defaultUseNameAsPrefix, normalizedName);
NormalizedSource nameBasedSource = new NamedSecretNormalizedSource(secretName, normalizedNamespace,
NormalizedSource namedBasedSource = new NamedSecretNormalizedSource(secretName, normalizedNamespace,
failFast, prefix);
normalizedSources.add(nameBasedSource);
normalizedSources.add(namedBasedSource);
if (!normalizedLabels.isEmpty()) {
NormalizedSource labelsBasedSource = new LabeledSecretNormalizedSource(normalizedNamespace, labels,
failFast);
normalizedSources.add(labelsBasedSource);
NormalizedSource labeledBasedSource = new LabeledSecretNormalizedSource(normalizedNamespace, labels,
failFast, prefix);
normalizedSources.add(labeledBasedSource);
}
return normalizedSources.build();

View File

@@ -52,7 +52,7 @@ class ConfigMapConfigPropertiesTests {
List<NormalizedSource> sources = properties.determineSources(new MockEnvironment());
Assertions.assertEquals(sources.size(), 1, "empty sources must generate a List with a single NormalizedSource");
Assertions.assertEquals(((NamedConfigMapNormalizedSource) sources.get(0)).prefix(), "",
Assertions.assertSame(((NamedConfigMapNormalizedSource) sources.get(0)).prefix(), ConfigUtils.Prefix.DEFAULT,
"empty sources must generate a List with a single NormalizedSource, where prefix is empty");
}
@@ -81,7 +81,7 @@ class ConfigMapConfigPropertiesTests {
List<NormalizedSource> sources = properties.determineSources(new MockEnvironment());
Assertions.assertEquals(sources.size(), 1, "empty sources must generate a List with a single NormalizedSource");
Assertions.assertEquals(((NamedConfigMapNormalizedSource) sources.get(0)).prefix(), "",
Assertions.assertSame(((NamedConfigMapNormalizedSource) sources.get(0)).prefix(), ConfigUtils.Prefix.DEFAULT,
"empty sources must generate a List with a single NormalizedSource, where prefix is empty,"
+ "no matter of 'spring.cloud.kubernetes.config.useNameAsPrefix' value");
}
@@ -114,7 +114,8 @@ class ConfigMapConfigPropertiesTests {
List<NormalizedSource> sources = properties.determineSources(new MockEnvironment());
Assertions.assertEquals(sources.size(), 1, "a single NormalizedSource is expected");
Assertions.assertEquals(((NamedConfigMapNormalizedSource) sources.get(0)).prefix(), "config-map-one");
Assertions.assertEquals(((NamedConfigMapNormalizedSource) sources.get(0)).prefix().prefixProvider().get(),
"config-map-one");
}
/**
@@ -160,9 +161,11 @@ class ConfigMapConfigPropertiesTests {
List<NormalizedSource> sources = properties.determineSources(new MockEnvironment());
Assertions.assertEquals(sources.size(), 3, "3 NormalizedSources are expected");
Assertions.assertEquals(((NamedConfigMapNormalizedSource) sources.get(0)).prefix(), "");
Assertions.assertEquals(((NamedConfigMapNormalizedSource) sources.get(1)).prefix(), "config-map-two");
Assertions.assertEquals(((NamedConfigMapNormalizedSource) sources.get(2)).prefix(), "config-map-three");
Assertions.assertSame(((NamedConfigMapNormalizedSource) sources.get(0)).prefix(), ConfigUtils.Prefix.DEFAULT);
Assertions.assertEquals(((NamedConfigMapNormalizedSource) sources.get(1)).prefix().prefixProvider().get(),
"config-map-two");
Assertions.assertEquals(((NamedConfigMapNormalizedSource) sources.get(2)).prefix().prefixProvider().get(),
"config-map-three");
}
/**
@@ -214,10 +217,13 @@ class ConfigMapConfigPropertiesTests {
List<NormalizedSource> sources = properties.determineSources(new MockEnvironment());
Assertions.assertEquals(sources.size(), 4, "4 NormalizedSources are expected");
Assertions.assertEquals(((NamedConfigMapNormalizedSource) sources.get(0)).prefix(), "one");
Assertions.assertEquals(((NamedConfigMapNormalizedSource) sources.get(1)).prefix(), "two");
Assertions.assertEquals(((NamedConfigMapNormalizedSource) sources.get(2)).prefix(), "three");
Assertions.assertEquals(((NamedConfigMapNormalizedSource) sources.get(3)).prefix(), "");
Assertions.assertEquals(((NamedConfigMapNormalizedSource) sources.get(0)).prefix().prefixProvider().get(),
"one");
Assertions.assertEquals(((NamedConfigMapNormalizedSource) sources.get(1)).prefix().prefixProvider().get(),
"two");
Assertions.assertEquals(((NamedConfigMapNormalizedSource) sources.get(2)).prefix().prefixProvider().get(),
"three");
Assertions.assertSame(((NamedConfigMapNormalizedSource) sources.get(3)).prefix(), ConfigUtils.Prefix.DEFAULT);
}
/**

View File

@@ -29,32 +29,55 @@ class ConfigUtilsTests {
@Test
void testExplicitPrefixSet() {
String result = ConfigUtils.findPrefix("explicitPrefix", null, false, "irrelevant");
Assertions.assertEquals(result, "explicitPrefix");
ConfigUtils.Prefix result = ConfigUtils.findPrefix("explicitPrefix", null, false, "irrelevant");
Assertions.assertSame(result, ConfigUtils.Prefix.KNOWN);
Assertions.assertEquals(result.prefixProvider().get(), "explicitPrefix");
}
@Test
void testUseNameAsPrefixTrue() {
String result = ConfigUtils.findPrefix("", Boolean.TRUE, false, "name-to-use");
Assertions.assertEquals(result, "name-to-use");
ConfigUtils.Prefix result = ConfigUtils.findPrefix("", Boolean.TRUE, false, "name-to-use");
Assertions.assertSame(result, ConfigUtils.Prefix.KNOWN);
Assertions.assertEquals(result.prefixProvider().get(), "name-to-use");
}
@Test
void testUseNameAsPrefixFalse() {
String result = ConfigUtils.findPrefix("", Boolean.FALSE, false, "name-not-to-use");
Assertions.assertEquals(result, "");
ConfigUtils.Prefix result = ConfigUtils.findPrefix("", Boolean.FALSE, false, "name-not-to-use");
Assertions.assertSame(result, ConfigUtils.Prefix.DEFAULT);
}
@Test
void testDefaultUseNameAsPrefixTrue() {
String result = ConfigUtils.findPrefix("", null, true, "name-to-use");
Assertions.assertEquals(result, "name-to-use");
ConfigUtils.Prefix result = ConfigUtils.findPrefix("", null, true, "name-to-use");
Assertions.assertSame(result, ConfigUtils.Prefix.KNOWN);
Assertions.assertEquals(result.prefixProvider().get(), "name-to-use");
}
@Test
void testNoMatch() {
String result = ConfigUtils.findPrefix("", null, false, "name-not-to-use");
Assertions.assertEquals(result, "");
ConfigUtils.Prefix result = ConfigUtils.findPrefix("", null, false, "name-not-to-use");
Assertions.assertSame(result, ConfigUtils.Prefix.DEFAULT);
}
@Test
void testUnsetEmpty() {
ConfigUtils.Prefix result = ConfigUtils.findPrefix("", null, false, "name-not-to-use");
Assertions.assertSame(result, ConfigUtils.Prefix.DEFAULT);
String expected = Assertions.assertDoesNotThrow(() -> result.prefixProvider().get());
Assertions.assertEquals("", expected);
}
@Test
void testDelayed() {
ConfigUtils.Prefix result = ConfigUtils.findPrefix(null, true, false, null);
Assertions.assertSame(result, ConfigUtils.Prefix.DELAYED);
IllegalArgumentException ex = Assertions.assertThrows(IllegalArgumentException.class,
() -> result.prefixProvider().get());
Assertions.assertEquals("prefix is delayed, needs to be taken elsewhere", ex.getMessage());
}
/**

View File

@@ -38,6 +38,23 @@ class LabeledSecretNormalizedSourceTests {
Assertions.assertEquals(left, right);
}
/*
* left and right instance have a different prefix, but the hashCode is the same; as
* it is not taken into consideration when computing hashCode
*/
@Test
void testEqualsAndHashCodePrefixDoesNotMatter() {
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);
Assertions.assertEquals(left.hashCode(), right.hashCode());
Assertions.assertEquals(left, right);
}
@Test
void testType() {
LabeledSecretNormalizedSource source = new LabeledSecretNormalizedSource("namespace", labels, false);
@@ -58,10 +75,12 @@ class LabeledSecretNormalizedSourceTests {
@Test
void testConstructorFields() {
LabeledSecretNormalizedSource source = new LabeledSecretNormalizedSource("namespace", labels, false);
ConfigUtils.Prefix prefix = ConfigUtils.findPrefix("prefix", false, false, "some");
LabeledSecretNormalizedSource source = new LabeledSecretNormalizedSource("namespace", labels, false, prefix);
Assertions.assertTrue(source.name().isEmpty());
Assertions.assertEquals(source.namespace().get(), "namespace");
Assertions.assertFalse(source.failFast());
Assertions.assertSame(source.prefix(), prefix);
}
}

View File

@@ -24,12 +24,18 @@ import org.junit.jupiter.api.Test;
*/
class NamedConfigMapNormalizedSourceTests {
private static final ConfigUtils.Prefix PREFIX = ConfigUtils.findPrefix("prefix", false, false, "prefix");
@Test
void testEqualsAndHashCode() {
NamedConfigMapNormalizedSource left = new NamedConfigMapNormalizedSource("name", "namespace", false, "prefix",
ConfigUtils.Prefix knownLeft = ConfigUtils.findPrefix("prefix", false, false, "some");
ConfigUtils.Prefix knownRight = ConfigUtils.findPrefix("prefix", false, false, "non-equal-prefix");
NamedConfigMapNormalizedSource left = new NamedConfigMapNormalizedSource("name", "namespace", false, knownLeft,
true);
NamedConfigMapNormalizedSource right = new NamedConfigMapNormalizedSource("name", "namespace", true,
"non-equal-prefix", false);
NamedConfigMapNormalizedSource right = new NamedConfigMapNormalizedSource("name", "namespace", true, knownRight,
false);
Assertions.assertEquals(left.hashCode(), right.hashCode());
Assertions.assertEquals(left, right);
@@ -37,21 +43,22 @@ class NamedConfigMapNormalizedSourceTests {
@Test
void testType() {
NamedConfigMapNormalizedSource one = new NamedConfigMapNormalizedSource("name", "namespace", false, "prefix",
NamedConfigMapNormalizedSource one = new NamedConfigMapNormalizedSource("name", "namespace", false, PREFIX,
true);
Assertions.assertSame(one.type(), NormalizedSourceType.NAMED_CONFIG_MAP);
}
@Test
void testTarget() {
NamedConfigMapNormalizedSource one = new NamedConfigMapNormalizedSource("name", "namespace", false, "prefix",
NamedConfigMapNormalizedSource one = new NamedConfigMapNormalizedSource("name", "namespace", false, PREFIX,
true);
Assertions.assertEquals(one.target(), "configmap");
}
@Test
void testConstructorFields() {
NamedConfigMapNormalizedSource one = new NamedConfigMapNormalizedSource("name", "namespace", false, "prefix",
NamedConfigMapNormalizedSource one = new NamedConfigMapNormalizedSource("name", "namespace", false, PREFIX,
true);
Assertions.assertEquals(one.name().get(), "name");
Assertions.assertEquals(one.namespace().get(), "namespace");

View File

@@ -24,10 +24,12 @@ import org.junit.jupiter.api.Test;
*/
class NamedSecretNormalizedSourceTests {
private static final ConfigUtils.Prefix PREFIX = ConfigUtils.findPrefix("prefix", false, false, "prefix");
@Test
void testEqualsAndHashCode() {
NamedSecretNormalizedSource left = new NamedSecretNormalizedSource("name", "namespace", false, "");
NamedSecretNormalizedSource right = new NamedSecretNormalizedSource("name", "namespace", true, "");
NamedSecretNormalizedSource left = new NamedSecretNormalizedSource("name", "namespace", false);
NamedSecretNormalizedSource right = new NamedSecretNormalizedSource("name", "namespace", true);
Assertions.assertEquals(left.hashCode(), right.hashCode());
Assertions.assertEquals(left, right);
@@ -35,23 +37,23 @@ class NamedSecretNormalizedSourceTests {
@Test
void testType() {
NamedSecretNormalizedSource source = new NamedSecretNormalizedSource("name", "namespace", false, "");
NamedSecretNormalizedSource source = new NamedSecretNormalizedSource("name", "namespace", 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);
Assertions.assertEquals(source.target(), "secret");
}
@Test
void testConstructorFields() {
NamedSecretNormalizedSource source = new NamedSecretNormalizedSource("name", "namespace", false, "prefix");
NamedSecretNormalizedSource source = new NamedSecretNormalizedSource("name", "namespace", false, PREFIX);
Assertions.assertEquals(source.name().get(), "name");
Assertions.assertEquals(source.namespace().get(), "namespace");
Assertions.assertFalse(source.failFast());
Assertions.assertEquals("prefix", source.prefix());
Assertions.assertSame(PREFIX, source.prefix());
}
}

View File

@@ -21,6 +21,7 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.jupiter.api.Assertions;
@@ -133,8 +134,7 @@ class SecretsConfigPropertiesTests {
List<NormalizedSource> sources = properties.determineSources(new MockEnvironment());
Assertions.assertEquals(sources.size(), 1, "empty sources must generate a List with a single NormalizedSource");
Assertions.assertEquals(((NamedSecretNormalizedSource) sources.get(0)).prefix(), "",
"empty sources must generate a List with a single NormalizedSource, where prefix is empty");
Assertions.assertSame(((NamedSecretNormalizedSource) sources.get(0)).prefix(), ConfigUtils.Prefix.DEFAULT);
}
/**
@@ -162,8 +162,8 @@ class SecretsConfigPropertiesTests {
List<NormalizedSource> sources = properties.determineSources(new MockEnvironment());
Assertions.assertEquals(sources.size(), 1, "empty sources must generate a List with a single NormalizedSource");
Assertions.assertEquals(((NamedSecretNormalizedSource) sources.get(0)).prefix(), "",
"empty sources must generate a List with a single NormalizedSource, where prefix is empty,"
Assertions.assertSame(((NamedSecretNormalizedSource) sources.get(0)).prefix(), ConfigUtils.Prefix.DEFAULT,
"empty sources must generate a List with a single NormalizedSource, where prefix is unset,"
+ "no matter of 'spring.cloud.kubernetes.secret.useNameAsPrefix' value");
}
@@ -195,7 +195,8 @@ class SecretsConfigPropertiesTests {
List<NormalizedSource> sources = properties.determineSources(new MockEnvironment());
Assertions.assertEquals(sources.size(), 1, "a single NormalizedSource is expected");
Assertions.assertEquals(((NamedSecretNormalizedSource) sources.get(0)).prefix(), "secret-one");
Assertions.assertEquals(((NamedSecretNormalizedSource) sources.get(0)).prefix().prefixProvider().get(),
"secret-one");
}
/**
@@ -241,9 +242,11 @@ class SecretsConfigPropertiesTests {
List<NormalizedSource> sources = properties.determineSources(new MockEnvironment());
Assertions.assertEquals(sources.size(), 3, "3 NormalizedSources are expected");
Assertions.assertEquals(((NamedSecretNormalizedSource) sources.get(0)).prefix(), "");
Assertions.assertEquals(((NamedSecretNormalizedSource) sources.get(1)).prefix(), "secret-two");
Assertions.assertEquals(((NamedSecretNormalizedSource) sources.get(2)).prefix(), "secret-three");
Assertions.assertSame(((NamedSecretNormalizedSource) sources.get(0)).prefix(), ConfigUtils.Prefix.DEFAULT);
Assertions.assertEquals(((NamedSecretNormalizedSource) sources.get(1)).prefix().prefixProvider().get(),
"secret-two");
Assertions.assertEquals(((NamedSecretNormalizedSource) sources.get(2)).prefix().prefixProvider().get(),
"secret-three");
}
/**
@@ -274,31 +277,105 @@ class SecretsConfigPropertiesTests {
properties.setNamespace("spring-k8s");
SecretsConfigProperties.Source one = new SecretsConfigProperties.Source();
one.setNamespace("secret-one");
one.setName("secret-one");
one.setUseNameAsPrefix(false);
one.setExplicitPrefix("one");
SecretsConfigProperties.Source two = new SecretsConfigProperties.Source();
two.setNamespace("secret-two");
two.setName("secret-two");
two.setUseNameAsPrefix(true);
two.setExplicitPrefix("two");
SecretsConfigProperties.Source three = new SecretsConfigProperties.Source();
three.setNamespace("secret-three");
three.setName("secret-three");
three.setExplicitPrefix("three");
SecretsConfigProperties.Source four = new SecretsConfigProperties.Source();
four.setNamespace("secret-four");
four.setName("secret-four");
properties.setSources(Arrays.asList(one, two, three, four));
List<NormalizedSource> sources = properties.determineSources(new MockEnvironment());
Assertions.assertEquals(sources.size(), 4, "4 NormalizedSources are expected");
Assertions.assertEquals(((NamedSecretNormalizedSource) sources.get(0)).prefix(), "one");
Assertions.assertEquals(((NamedSecretNormalizedSource) sources.get(1)).prefix(), "two");
Assertions.assertEquals(((NamedSecretNormalizedSource) sources.get(2)).prefix(), "three");
Assertions.assertEquals(((NamedSecretNormalizedSource) sources.get(3)).prefix(), "");
Assertions.assertEquals(((NamedSecretNormalizedSource) sources.get(0)).prefix().prefixProvider().get(), "one");
Assertions.assertEquals(((NamedSecretNormalizedSource) sources.get(1)).prefix().prefixProvider().get(), "two");
Assertions.assertEquals(((NamedSecretNormalizedSource) sources.get(2)).prefix().prefixProvider().get(),
"three");
Assertions.assertSame(((NamedSecretNormalizedSource) sources.get(3)).prefix(), ConfigUtils.Prefix.DEFAULT);
}
/**
* <pre>
* spring:
* cloud:
* kubernetes:
* secrets:
* useNameAsPrefix: false
* namespace: spring-k8s
* sources:
* - labels:
* - name: first-label
* value: secret-one
* useNameAsPrefix: false
* explicitPrefix: one
* - labels:
* - name: second-label
* value: secret-two
* useNameAsPrefix: true
* explicitPrefix: two
* - labels:
* - name: third-label
* value: secret-three
* explicitPrefix: three
* - labels:
* - name: fourth-label
* value: secret-four
* </pre>
*
*/
@Test
void testLabelsMultipleCases() {
SecretsConfigProperties properties = new SecretsConfigProperties();
properties.setUseNameAsPrefix(false);
properties.setNamespace("spring-k8s");
SecretsConfigProperties.Source one = new SecretsConfigProperties.Source();
one.setLabels(Map.of("first-label", "secret-one"));
one.setUseNameAsPrefix(false);
one.setExplicitPrefix("one");
SecretsConfigProperties.Source two = new SecretsConfigProperties.Source();
two.setLabels(Map.of("second-label", "secret-two"));
two.setUseNameAsPrefix(true);
two.setExplicitPrefix("two");
SecretsConfigProperties.Source three = new SecretsConfigProperties.Source();
three.setLabels(Map.of("third-label", "secret-three"));
three.setExplicitPrefix("three");
SecretsConfigProperties.Source four = new SecretsConfigProperties.Source();
four.setLabels(Map.of("fourth-label", "secret-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");
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);
Set<NormalizedSource> set = new LinkedHashSet<>(sources);
Assertions.assertEquals(5, set.size());
}
}

View File

@@ -77,7 +77,7 @@ public class KubernetesConfigServerAutoConfiguration {
namespaces.forEach(space -> {
NamedConfigMapNormalizedSource source = new NamedConfigMapNormalizedSource(applicationName, space,
false, "", true);
false, true);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(coreApi, source, space,
springEnv);
@@ -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);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(coreApi, source, space,
springEnv);
propertySources.add(new KubernetesClientSecretsPropertySource(context));

View File

@@ -101,12 +101,12 @@ class KubernetesEnvironmentRepositoryTests {
kubernetesPropertySourceSuppliers.add((coreApi, applicationName, namespace, springEnv) -> {
List<MapPropertySource> propertySources = new ArrayList<>();
NormalizedSource defaultSource = new NamedConfigMapNormalizedSource(applicationName, "default", false, "",
NormalizedSource defaultSource = new NamedConfigMapNormalizedSource(applicationName, "default", false,
true);
KubernetesClientConfigContext defaultContext = new KubernetesClientConfigContext(coreApi, defaultSource,
"default", springEnv);
NormalizedSource devSource = new NamedConfigMapNormalizedSource(applicationName, "dev", false, "", true);
NormalizedSource devSource = new NamedConfigMapNormalizedSource(applicationName, "dev", false, true);
KubernetesClientConfigContext devContext = new KubernetesClientConfigContext(coreApi, devSource, "dev",
springEnv);
@@ -117,7 +117,7 @@ class KubernetesEnvironmentRepositoryTests {
kubernetesPropertySourceSuppliers.add((coreApi, applicationName, namespace, springEnv) -> {
List<MapPropertySource> propertySources = new ArrayList<>();
NormalizedSource source = new NamedSecretNormalizedSource(applicationName, "default", false, "");
NormalizedSource source = new NamedSecretNormalizedSource(applicationName, "default", false);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(coreApi, source, "default",
springEnv);

View File

@@ -17,8 +17,10 @@
package org.springframework.cloud.kubernetes.fabric8.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;
@@ -29,6 +31,7 @@ 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.commons.config.ConfigUtils.onException;
@@ -64,11 +67,12 @@ final class LabeledSecretContextToSourceDataProvider implements Supplier<Fabric8
return context -> {
LabeledSecretNormalizedSource source = ((LabeledSecretNormalizedSource) context.normalizedSource());
Set<String> propertySourceNames = new LinkedHashSet<>();
Map<String, String> labels = source.labels();
Map<String, Object> result = new HashMap<>();
String namespace = context.namespace();
String sourceName = String.join(PROPERTY_SOURCE_NAME_SEPARATOR, labels.keySet());
String sourceNameFromLabels = String.join(PROPERTY_SOURCE_NAME_SEPARATOR, labels.keySet());
try {
@@ -77,9 +81,43 @@ final class LabeledSecretContextToSourceDataProvider implements Supplier<Fabric8
.getItems();
if (!secrets.isEmpty()) {
secrets.forEach(secret -> result.putAll(dataFromSecret(secret, namespace)));
sourceName = secrets.stream().map(Secret::getMetadata).map(ObjectMeta::getName)
for (Secret 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(Secret::getMetadata).map(ObjectMeta::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);
}
else {
LOG.info("No Secret(s) with labels '" + labels + "' in namespace '" + namespace + "' found.");
@@ -91,8 +129,12 @@ final class LabeledSecretContextToSourceDataProvider implements Supplier<Fabric8
onException(source.failFast(), message, e);
}
String propertySourceName = ConfigUtils.sourceName(source.target(), sourceName, namespace);
return new SourceData(propertySourceName, result);
// 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);
};
}

View File

@@ -97,9 +97,10 @@ final class NamedConfigMapContextToSourceDataProvider implements Supplier<Fabric
}
}
if (!"".equals(source.prefix())) {
PrefixContext prefixContext = new PrefixContext(result, source.prefix(), namespace,
propertySourceNames);
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);
}

View File

@@ -71,9 +71,11 @@ final class NamedSecretContextToSourceDataProvider implements Supplier<Fabric8Co
else {
result = dataFromSecret(secret, namespace);
if (!"".equals(source.prefix())) {
PrefixContext prefixContext = new PrefixContext(result, source.prefix(), namespace,
propertySourceNames);
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);
}
}

View File

@@ -75,7 +75,7 @@ public class ConfigMapsTest {
.build();
mockClient.configMaps().inNamespace("test").create(configMap);
NormalizedSource source = new NamedConfigMapNormalizedSource(configMapName, "test", false, "", false);
NormalizedSource source = new NamedConfigMapNormalizedSource(configMapName, "test", false, false);
Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, source, "", new MockEnvironment());
Fabric8ConfigMapPropertySource cmps = new Fabric8ConfigMapPropertySource(context);
@@ -92,7 +92,7 @@ public class ConfigMapsTest {
.addToData("application.yaml", ConfigMapTestUtil.readResourceFile("application.yaml")).build();
mockClient.configMaps().inNamespace("test").create(configMap);
NormalizedSource source = new NamedConfigMapNormalizedSource(configMapName, "test", false, "", false);
NormalizedSource source = new NamedConfigMapNormalizedSource(configMapName, "test", false, false);
Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, source, "", new MockEnvironment());
Fabric8ConfigMapPropertySource cmps = new Fabric8ConfigMapPropertySource(context);
@@ -109,7 +109,7 @@ public class ConfigMapsTest {
.addToData("adhoc.yml", ConfigMapTestUtil.readResourceFile("adhoc.yml")).build();
mockClient.configMaps().inNamespace("test").create(configMap);
NormalizedSource source = new NamedConfigMapNormalizedSource(configMapName, "test", false, "", false);
NormalizedSource source = new NamedConfigMapNormalizedSource(configMapName, "test", false, false);
Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, source, "", new MockEnvironment());
Fabric8ConfigMapPropertySource cmps = new Fabric8ConfigMapPropertySource(context);
@@ -126,7 +126,7 @@ public class ConfigMapsTest {
.addToData("application.properties", "somevalue").build();
mockClient.configMaps().inNamespace("test").create(configMap);
NormalizedSource source = new NamedConfigMapNormalizedSource(configMapName, "namespace", false, "", false);
NormalizedSource source = new NamedConfigMapNormalizedSource(configMapName, "namespace", false, false);
Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, source, "", new MockEnvironment());
Fabric8ConfigMapPropertySource cmps = new Fabric8ConfigMapPropertySource(context);
@@ -141,7 +141,7 @@ public class ConfigMapsTest {
.addToData("application.yaml", "somevalue").build();
mockClient.configMaps().inNamespace("test").create(configMap);
NormalizedSource source = new NamedConfigMapNormalizedSource(configMapName, "namespace", false, "", false);
NormalizedSource source = new NamedConfigMapNormalizedSource(configMapName, "namespace", false, false);
Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, source, "", new MockEnvironment());
Fabric8ConfigMapPropertySource cmps = new Fabric8ConfigMapPropertySource(context);
@@ -157,7 +157,7 @@ public class ConfigMapsTest {
.addToData("adhoc.properties", ConfigMapTestUtil.readResourceFile("adhoc.properties")).build();
mockClient.configMaps().inNamespace("test").create(configMap);
NormalizedSource source = new NamedConfigMapNormalizedSource(configMapName, "test", false, "", false);
NormalizedSource source = new NamedConfigMapNormalizedSource(configMapName, "test", false, false);
Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, source, "", new MockEnvironment());
Fabric8ConfigMapPropertySource cmps = new Fabric8ConfigMapPropertySource(context);

View File

@@ -64,7 +64,7 @@ public class EventBasedConfigurationChangeDetectorTests {
when(mixedOperation.inNamespace("default")).thenReturn(mixedOperation);
when(k8sClient.getNamespace()).thenReturn("default");
NormalizedSource source = new NamedConfigMapNormalizedSource("myconfigmap", "default", true, "", false);
NormalizedSource source = new NamedConfigMapNormalizedSource("myconfigmap", "default", true, false);
Fabric8ConfigContext context = new Fabric8ConfigContext(k8sClient, source, "default", new MockEnvironment());
Fabric8ConfigMapPropertySource fabric8ConfigMapPropertySource = new Fabric8ConfigMapPropertySource(context);
env.getPropertySources().addFirst(new BootstrapPropertySource<>(fabric8ConfigMapPropertySource));

View File

@@ -25,6 +25,7 @@ import org.mockito.Mockito;
import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
import org.springframework.cloud.kubernetes.commons.config.ConfigMapConfigProperties;
import org.springframework.cloud.kubernetes.commons.config.ConfigUtils;
import org.springframework.cloud.kubernetes.commons.config.NamedConfigMapNormalizedSource;
import org.springframework.cloud.kubernetes.commons.config.NamespaceResolutionFailedException;
import org.springframework.cloud.kubernetes.commons.config.NormalizedSource;
@@ -45,6 +46,8 @@ class Fabric8ConfigMapPropertySourceLocatorTests {
private final DefaultKubernetesClient client = Mockito.mock(DefaultKubernetesClient.class);
private static final ConfigUtils.Prefix PREFIX = ConfigUtils.findPrefix("prefix", false, false, "irrelevant");
@Test
void locateShouldThrowExceptionOnFailureWhenFailFastIsEnabled() {
String name = "my-config";
@@ -95,7 +98,7 @@ class Fabric8ConfigMapPropertySourceLocatorTests {
Mockito.when(client.getNamespace()).thenReturn(null);
Fabric8ConfigMapPropertySourceLocator source = new Fabric8ConfigMapPropertySourceLocator(client,
configMapConfigProperties, new KubernetesNamespaceProvider(new MockEnvironment()));
NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("name", null, false, "prefix", false);
NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("name", null, false, PREFIX, false);
assertThatThrownBy(() -> source.getMapPropertySource(normalizedSource, new MockEnvironment()))
.isInstanceOf(NamespaceResolutionFailedException.class);
}

View File

@@ -23,6 +23,7 @@ import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
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.mock.env.MockEnvironment;
@@ -43,6 +44,8 @@ class Fabric8ConfigMapPropertySourceTests {
private final DefaultKubernetesClient client = Mockito.mock(DefaultKubernetesClient.class);
private static final ConfigUtils.Prefix DEFAULT = ConfigUtils.findPrefix("default", false, false, "irrelevant");
@Test
void constructorShouldThrowExceptionOnFailureWhenFailFastIsEnabled() {
final String name = "my-config";
@@ -50,7 +53,7 @@ class Fabric8ConfigMapPropertySourceTests {
final String path = String.format("/api/v1/namespaces/%s/configmaps/%s", namespace, name);
mockServer.expect().withPath(path).andReturn(500, "Internal Server Error").once();
NormalizedSource source = new NamedConfigMapNormalizedSource(name, namespace, true, "default", true);
NormalizedSource source = new NamedConfigMapNormalizedSource(name, namespace, true, DEFAULT, true);
Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, source, "default", new MockEnvironment());
assertThatThrownBy(() -> new Fabric8ConfigMapPropertySource(context)).isInstanceOf(IllegalStateException.class)
.hasMessage("Unable to read ConfigMap with name '" + name + "' in namespace '" + namespace + "'");
@@ -63,7 +66,7 @@ class Fabric8ConfigMapPropertySourceTests {
final String path = String.format("/api/v1/namespaces/%s/configmaps/%s", namespace, name);
mockServer.expect().withPath(path).andReturn(500, "Internal Server Error").once();
NormalizedSource source = new NamedConfigMapNormalizedSource(name, namespace, false, "", false);
NormalizedSource source = new NamedConfigMapNormalizedSource(name, namespace, false, false);
Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, source, "", new MockEnvironment());
assertThatNoException().isThrownBy(() -> new Fabric8ConfigMapPropertySource(context));
}
@@ -72,7 +75,7 @@ class Fabric8ConfigMapPropertySourceTests {
void constructorWithClientNamespaceMustNotFail() {
Mockito.when(client.getNamespace()).thenReturn("namespace");
NormalizedSource source = new NamedConfigMapNormalizedSource("configmap", null, false, "", false);
NormalizedSource source = new NamedConfigMapNormalizedSource("configmap", null, false, false);
Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, source, "", new MockEnvironment());
assertThat(new Fabric8ConfigMapPropertySource(context)).isNotNull();
}
@@ -81,7 +84,7 @@ class Fabric8ConfigMapPropertySourceTests {
void constructorWithNamespaceMustNotFail() {
Mockito.when(client.getNamespace()).thenReturn(null);
NormalizedSource source = new NamedConfigMapNormalizedSource("configMap", null, false, "", true);
NormalizedSource source = new NamedConfigMapNormalizedSource("configMap", null, false, true);
Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, source, "", new MockEnvironment());
assertThat(new Fabric8ConfigMapPropertySource(context)).isNotNull();
}

View File

@@ -49,7 +49,7 @@ class Fabric8SecretsPropertySourceMockTests {
final String namespace = "default";
final String path = String.format("/api/v1/namespaces/%s/secrets/%s", namespace, name);
NamedSecretNormalizedSource named = new NamedSecretNormalizedSource(name, namespace, true, "");
NamedSecretNormalizedSource named = new NamedSecretNormalizedSource(name, namespace, true);
Fabric8ConfigContext context = new Fabric8ConfigContext(client, named, "default", new MockEnvironment());
mockServer.expect().withPath(path).andReturn(500, "Internal Server Error").once();
@@ -77,7 +77,7 @@ class Fabric8SecretsPropertySourceMockTests {
final String namespace = "default";
final String path = String.format("/api/v1/namespaces/%s/secrets/%s", namespace, name);
NamedSecretNormalizedSource named = new NamedSecretNormalizedSource(name, namespace, false, "");
NamedSecretNormalizedSource named = new NamedSecretNormalizedSource(name, namespace, false);
Fabric8ConfigContext context = new Fabric8ConfigContext(client, named, "default", new MockEnvironment());
mockServer.expect().withPath(path).andReturn(500, "Internal Server Error").once();

View File

@@ -18,6 +18,7 @@ package org.springframework.cloud.kubernetes.fabric8.config;
import java.util.Base64;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -31,6 +32,7 @@ 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.LabeledSecretNormalizedSource;
import org.springframework.cloud.kubernetes.commons.config.NormalizedSource;
import org.springframework.cloud.kubernetes.commons.config.SourceData;
@@ -104,7 +106,7 @@ class LabeledSecretContextToSourceDataProviderTests {
}
/**
* we have three secret deployed. two of them have labels that match (color=red), one
* we have three secrets deployed. two of them have labels that match (color=red), one
* does not (color=blue).
*/
@Test
@@ -186,4 +188,83 @@ class LabeledSecretContextToSourceDataProviderTests {
Assertions.assertEquals(Map.of("secretName", "secretValue"), sourceData.sourceData());
}
/**
* one secret with name : "blue-secret" 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() {
Secret secret = new SecretBuilder().withNewMetadata().withName("blue-secret")
.withLabels(Collections.singletonMap("color", "blue")).endMetadata()
.addToData("what-color", Base64.getEncoder().encodeToString("blue-color".getBytes())).build();
mockClient.secrets().inNamespace(NAMESPACE).create(secret);
ConfigUtils.Prefix mePrefix = ConfigUtils.findPrefix("me", false, false, "irrelevant");
NormalizedSource normalizedSource = new LabeledSecretNormalizedSource(NAMESPACE,
Collections.singletonMap("color", "blue"), true, mePrefix);
Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE,
new MockEnvironment());
Fabric8ContextToSourceData data = new LabeledSecretContextToSourceDataProvider().get();
SourceData sourceData = data.apply(context);
Assertions.assertEquals("secret.blue-secret.default", sourceData.sourceName());
Assertions.assertEquals(Map.of("me.what-color", "blue-color"), sourceData.sourceData());
}
/**
* two secrets are deployed (name:blue-secret, name:another-blue-secret) 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 secret names.
*
*/
@Test
void testTwoSecretsWithPrefix() {
Secret blueSecret = new SecretBuilder().withNewMetadata().withName("blue-secret")
.withLabels(Collections.singletonMap("color", "blue")).endMetadata()
.addToData("first", Base64.getEncoder().encodeToString("blue".getBytes())).build();
Secret anotherBlue = new SecretBuilder().withNewMetadata().withName("another-blue-secret")
.withLabels(Collections.singletonMap("color", "blue")).endMetadata()
.addToData("second", Base64.getEncoder().encodeToString("blue".getBytes())).build();
mockClient.secrets().inNamespace(NAMESPACE).create(blueSecret);
mockClient.secrets().inNamespace(NAMESPACE).create(anotherBlue);
NormalizedSource normalizedSource = new LabeledSecretNormalizedSource(NAMESPACE,
Collections.singletonMap("color", "blue"), true, ConfigUtils.Prefix.DELAYED);
Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE,
new MockEnvironment());
Fabric8ContextToSourceData data = new LabeledSecretContextToSourceDataProvider().get();
SourceData sourceData = data.apply(context);
// maps don't have a defined order, so assert components separately
Assertions.assertEquals(46, sourceData.sourceName().length());
Assertions.assertTrue(sourceData.sourceName().contains("secret"));
Assertions.assertTrue(sourceData.sourceName().contains("blue-secret"));
Assertions.assertTrue(sourceData.sourceName().contains("another-blue-secret"));
Assertions.assertTrue(sourceData.sourceName().contains("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-secret.blue-secret.first");
}
Assertions.assertEquals(secondKey, "another-blue-secret.blue-secret.second");
Assertions.assertEquals(properties.get(firstKey), "blue");
Assertions.assertEquals(properties.get(secondKey), "blue");
}
}

View File

@@ -30,6 +30,7 @@ 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;
@@ -48,6 +49,8 @@ class NamedConfigMapContextToSourceDataProviderTests {
private static KubernetesClient mockClient;
private static final ConfigUtils.Prefix PREFIX = ConfigUtils.findPrefix("some", false, false, "irrelevant");
@BeforeAll
static void beforeAll() {
@@ -77,7 +80,7 @@ class NamedConfigMapContextToSourceDataProviderTests {
mockClient.configMaps().inNamespace(NAMESPACE).create(configMap);
NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("blue", NAMESPACE, true, "", false);
NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("blue", NAMESPACE, true, false);
Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE,
new MockEnvironment());
@@ -100,7 +103,7 @@ class NamedConfigMapContextToSourceDataProviderTests {
mockClient.configMaps().inNamespace(NAMESPACE).create(configMap);
NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("red", NAMESPACE, true, "", false);
NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("red", NAMESPACE, true, false);
Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE,
new MockEnvironment());
@@ -131,7 +134,7 @@ class NamedConfigMapContextToSourceDataProviderTests {
// add one more profile and specify that we want profile based config maps
MockEnvironment env = new MockEnvironment();
env.setActiveProfiles("with-profile");
NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("red", NAMESPACE, true, "", true);
NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("red", NAMESPACE, true, true);
Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, env);
@@ -167,7 +170,7 @@ class NamedConfigMapContextToSourceDataProviderTests {
// also append prefix
MockEnvironment env = new MockEnvironment();
env.setActiveProfiles("with-profile");
NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("red", NAMESPACE, true, "some", true);
NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("red", NAMESPACE, true, PREFIX, true);
Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, env);
@@ -207,7 +210,7 @@ class NamedConfigMapContextToSourceDataProviderTests {
// also append prefix
MockEnvironment env = new MockEnvironment();
env.setActiveProfiles("with-taste", "with-shape");
NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("red", NAMESPACE, true, "some", true);
NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("red", NAMESPACE, true, PREFIX, true);
Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, env);
@@ -232,8 +235,7 @@ class NamedConfigMapContextToSourceDataProviderTests {
mockClient.configMaps().inNamespace(NAMESPACE).create(configMap);
NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("application", NAMESPACE, true, "",
false);
NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("application", NAMESPACE, true, false);
Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE,
new MockEnvironment());
@@ -259,8 +261,7 @@ class NamedConfigMapContextToSourceDataProviderTests {
mockClient.configMaps().inNamespace(NAMESPACE).create(configMap);
// different namespace
NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("red", NAMESPACE + "nope", true, "",
false);
NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("red", NAMESPACE + "nope", true, false);
Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE,
new MockEnvironment());

View File

@@ -76,7 +76,7 @@ class NamedSecretContextToSourceDataProviderTests {
mockClient.secrets().inNamespace(NAMESPACE).create(secret);
NormalizedSource normalizedSource = new NamedSecretNormalizedSource("red", NAMESPACE, true, "");
NormalizedSource normalizedSource = new NamedSecretNormalizedSource("red", NAMESPACE, true);
Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE,
new MockEnvironment());
@@ -108,7 +108,7 @@ class NamedSecretContextToSourceDataProviderTests {
mockClient.secrets().inNamespace(NAMESPACE).create(blue);
mockClient.secrets().inNamespace(NAMESPACE).create(yellow);
NormalizedSource normalizedSource = new NamedSecretNormalizedSource("red", NAMESPACE, true, "");
NormalizedSource normalizedSource = new NamedSecretNormalizedSource("red", NAMESPACE, true);
Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE,
new MockEnvironment());
@@ -132,7 +132,7 @@ class NamedSecretContextToSourceDataProviderTests {
mockClient.secrets().inNamespace(NAMESPACE).create(pink);
NormalizedSource normalizedSource = new NamedSecretNormalizedSource("blue", NAMESPACE, true, "");
NormalizedSource normalizedSource = new NamedSecretNormalizedSource("blue", NAMESPACE, true);
Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE,
new MockEnvironment());
@@ -158,7 +158,7 @@ class NamedSecretContextToSourceDataProviderTests {
mockClient.secrets().inNamespace(NAMESPACE).create(secret);
// different namespace
NormalizedSource normalizedSource = new NamedSecretNormalizedSource("red", NAMESPACE + "nope", true, "");
NormalizedSource normalizedSource = new NamedSecretNormalizedSource("red", NAMESPACE + "nope", true);
Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE,
new MockEnvironment());

View File

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

View File

@@ -0,0 +1,41 @@
/*
* 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.fabric8.config.labeled_secret_with_prefix;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
import org.junit.jupiter.api.BeforeAll;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @author wind57
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = LabeledSecretWithPrefixApp.class,
properties = { "spring.cloud.bootstrap.name=labeled-secret-with-prefix",
"spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true" })
@EnableKubernetesMockClient(crud = true, https = false)
class LabeledSecretWithPrefixBootstrapTests extends LabeledSecretWithPrefixTests {
private static KubernetesClient mockClient;
@BeforeAll
static void setUpBeforeClass() {
setUpBeforeClass(mockClient);
}
}

View File

@@ -0,0 +1,41 @@
/*
* 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.fabric8.config.labeled_secret_with_prefix;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
import org.junit.jupiter.api.BeforeAll;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @author wind57
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = LabeledSecretWithPrefixApp.class,
properties = { "spring.application.name=named-secret-with-prefix", "spring.main.cloud-platform=KUBERNETES",
"spring.config.import=kubernetes:,classpath:./labeled-secret-with-prefix.yaml" })
@EnableKubernetesMockClient(crud = true, https = false)
class LabeledSecretWithPrefixConfigDataTests extends LabeledSecretWithPrefixTests {
private static KubernetesClient mockClient;
@BeforeAll
static void setUpBeforeClass() {
setUpBeforeClass(mockClient);
}
}

View File

@@ -0,0 +1,141 @@
/*
* 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.fabric8.config.labeled_secret_with_prefix;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Collections;
import java.util.Map;
import io.fabric8.kubernetes.api.model.SecretBuilder;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.KubernetesClient;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.reactive.server.WebTestClient;
/**
* @author wind57
*/
abstract class LabeledSecretWithPrefixTests {
private static KubernetesClient mockClient;
@Autowired
private WebTestClient webClient;
static void setUpBeforeClass(KubernetesClient mockClient) {
LabeledSecretWithPrefixTests.mockClient = mockClient;
// Configure the kubernetes master url to point to the mock server
System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl());
System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true");
System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false");
System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY, "false");
System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, "test");
System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true");
Map<String, String> one = Collections.singletonMap("one.property",
Base64.getEncoder().encodeToString("one".getBytes(StandardCharsets.UTF_8)));
createSecret("secret-one", one, Collections.singletonMap("letter", "a"));
Map<String, String> two = Collections.singletonMap("property",
Base64.getEncoder().encodeToString("two".getBytes(StandardCharsets.UTF_8)));
createSecret("secret-two", two, Collections.singletonMap("letter", "b"));
Map<String, String> three = Collections.singletonMap("property",
Base64.getEncoder().encodeToString("three".getBytes(StandardCharsets.UTF_8)));
createSecret("secret-three", three, Collections.singletonMap("letter", "c"));
Map<String, String> four = Collections.singletonMap("property",
Base64.getEncoder().encodeToString("four".getBytes(StandardCharsets.UTF_8)));
createSecret("secret-four", four, Collections.singletonMap("letter", "d"));
}
private static void createSecret(String name, Map<String, String> data, Map<String, String> labels) {
mockClient.secrets().inNamespace("spring-k8s").create(new SecretBuilder().withNewMetadata().withName(name)
.withLabels(labels).endMetadata().addToData(data).build());
}
/**
* <pre>
* 'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
* 'spring.cloud.kubernetes.secrets.sources[0].useNameAsPrefix=false'
* ("one.property", "one")
*
* As such: @ConfigurationProperties("one")
* </pre>
*/
@Test
void testOne() {
this.webClient.get().uri("/labeled-secret/prefix/one").exchange().expectStatus().isOk().expectBody(String.class)
.value(Matchers.equalTo("one"));
}
/**
* <pre>
* 'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
* 'spring.cloud.kubernetes.secrets.sources[1].explicitPrefix=two'
* ("property", "two")
*
* As such: @ConfigurationProperties("two")
* </pre>
*/
@Test
void testTwo() {
this.webClient.get().uri("/labeled-secret/prefix/two").exchange().expectStatus().isOk().expectBody(String.class)
.value(Matchers.equalTo("two"));
}
/**
* <pre>
* 'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
* 'spring.cloud.kubernetes.secrets.sources[2].labels=letter:c'
* ("property", "three")
*
* We find the secret by labels, and use it's name as the prefix.
*
* As such: @ConfigurationProperties(prefix = "secret-three")
* </pre>
*/
@Test
void testThree() {
this.webClient.get().uri("/labeled-secret/prefix/three").exchange().expectStatus().isOk()
.expectBody(String.class).value(Matchers.equalTo("three"));
}
/**
* <pre>
* 'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
* 'spring.cloud.kubernetes.secrets.sources[3].labels=letter:d'
* ("property", "four")
*
* We find the secret by labels, and use it's name as the prefix.
*
* As such: @ConfigurationProperties(prefix = "secret-four")
* </pre>
*/
@Test
void testFour() {
this.webClient.get().uri("/labeled-secret/prefix/four").exchange().expectStatus().isOk()
.expectBody(String.class).value(Matchers.equalTo("four"));
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,21 @@
spring:
application:
name: labeled-secret-with-prefix
cloud:
kubernetes:
secrets:
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