From df5066b9e535ec42806c0970a8a9de204b0a4867 Mon Sep 17 00:00:00 2001 From: erabii Date: Thu, 26 May 2022 16:02:57 +0300 Subject: [PATCH] Labeled secrets prefix support (#1017) --- .../main/asciidoc/property-source-config.adoc | 65 ++++++++ ...eledSecretContextToSourceDataProvider.java | 52 ++++++- ...dConfigMapContextToSourceDataProvider.java | 7 +- ...amedSecretContextToSourceDataProvider.java | 7 +- ...tesClientConfigMapPropertySourceTests.java | 17 ++- ...netesClientSecretsPropertySourceTests.java | 6 +- ...ecretContextToSourceDataProviderTests.java | 91 +++++++++++ ...igMapContextToSourceDataProviderTests.java | 18 ++- ...ecretContextToSourceDataProviderTests.java | 8 +- .../LabeledSecretWithPrefixApp.java | 35 +++++ ...LabeledSecretWithPrefixBootstrapTests.java | 30 ++++ ...abeledSecretWithPrefixConfigDataTests.java | 61 ++++++++ .../LabeledSecretWithPrefixTests.java | 113 ++++++++++++++ .../LabeledSecretWithPrefixController.java | 64 ++++++++ .../properties/Four.java | 34 +++++ .../properties/One.java | 34 +++++ .../properties/Three.java | 34 +++++ .../properties/Two.java | 34 +++++ .../NamedSecretWithPrefixBootstrapTests.java | 3 +- .../NamedSecretWithPrefixConfigDataTests.java | 3 +- ...eledSecretWithPrefixConfigurationStub.java | 110 ++++++++++++++ .../test/resources/META-INF/spring.factories | 3 +- .../resources/labeled-secret-with-prefix.yaml | 21 +++ .../config/ConfigMapConfigProperties.java | 6 +- .../commons/config/ConfigUtils.java | 70 ++++++++- .../config/LabeledSecretNormalizedSource.java | 14 ++ .../NamedConfigMapNormalizedSource.java | 13 +- .../config/NamedSecretNormalizedSource.java | 13 +- .../config/SecretsConfigProperties.java | 19 +-- .../ConfigMapConfigPropertiesTests.java | 26 ++-- .../commons/config/ConfigUtilsTests.java | 43 ++++-- .../LabeledSecretNormalizedSourceTests.java | 21 ++- .../NamedConfigMapNormalizedSourceTests.java | 19 ++- .../NamedSecretNormalizedSourceTests.java | 14 +- .../config/SecretsConfigPropertiesTests.java | 109 ++++++++++++-- ...bernetesConfigServerAutoConfiguration.java | 4 +- .../KubernetesEnvironmentRepositoryTests.java | 6 +- ...eledSecretContextToSourceDataProvider.java | 52 ++++++- ...dConfigMapContextToSourceDataProvider.java | 7 +- ...amedSecretContextToSourceDataProvider.java | 8 +- .../fabric8/config/ConfigMapsTest.java | 12 +- ...BasedConfigurationChangeDetectorTests.java | 2 +- ...c8ConfigMapPropertySourceLocatorTests.java | 5 +- .../Fabric8ConfigMapPropertySourceTests.java | 11 +- ...Fabric8SecretsPropertySourceMockTests.java | 4 +- ...ecretContextToSourceDataProviderTests.java | 83 ++++++++++- ...igMapContextToSourceDataProviderTests.java | 19 +-- ...ecretContextToSourceDataProviderTests.java | 8 +- .../LabeledSecretWithPrefixApp.java | 35 +++++ ...LabeledSecretWithPrefixBootstrapTests.java | 41 +++++ ...abeledSecretWithPrefixConfigDataTests.java | 41 +++++ .../LabeledSecretWithPrefixTests.java | 141 ++++++++++++++++++ .../LabeledSecretWithPrefixController.java | 64 ++++++++ .../properties/Four.java | 34 +++++ .../properties/One.java | 34 +++++ .../properties/Three.java | 34 +++++ .../properties/Two.java | 34 +++++ .../resources/labeled-secret-with-prefix.yaml | 21 +++ 58 files changed, 1763 insertions(+), 154 deletions(-) create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/LabeledSecretWithPrefixApp.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/LabeledSecretWithPrefixBootstrapTests.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/LabeledSecretWithPrefixConfigDataTests.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/LabeledSecretWithPrefixTests.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/controller/LabeledSecretWithPrefixController.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/properties/Four.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/properties/One.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/properties/Three.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/properties/Two.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/LabeledSecretWithPrefixConfigurationStub.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/resources/labeled-secret-with-prefix.yaml create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/LabeledSecretWithPrefixApp.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/LabeledSecretWithPrefixBootstrapTests.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/LabeledSecretWithPrefixConfigDataTests.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/LabeledSecretWithPrefixTests.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/controller/LabeledSecretWithPrefixController.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/properties/Four.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/properties/One.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/properties/Three.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/properties/Two.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/resources/labeled-secret-with-prefix.yaml diff --git a/docs/src/main/asciidoc/property-source-config.adoc b/docs/src/main/asciidoc/property-source-config.adoc index cbd9ca3a..4f0c5eb9 100644 --- a/docs/src/main/asciidoc/property-source-config.adoc +++ b/docs/src/main/asciidoc/property-source-config.adoc @@ -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: diff --git a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/LabeledSecretContextToSourceDataProvider.java b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/LabeledSecretContextToSourceDataProvider.java index ef802fae..85cef8cb 100644 --- a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/LabeledSecretContextToSourceDataProvider.java +++ b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/LabeledSecretContextToSourceDataProvider.java @@ -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 result = new HashMap<>(); LabeledSecretNormalizedSource source = (LabeledSecretNormalizedSource) context.normalizedSource(); + Set propertySourceNames = new LinkedHashSet<>(); Map 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 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 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); } diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapPropertySourceTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapPropertySourceTests.java index b7c42c8c..49dce03c 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapPropertySourceTests.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapPropertySourceTests.java @@ -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()); diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientSecretsPropertySourceTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientSecretsPropertySourceTests.java index 84984067..b0cbfd96 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientSecretsPropertySourceTests.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientSecretsPropertySourceTests.java @@ -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()); diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/LabeledSecretContextToSourceDataProviderTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/LabeledSecretContextToSourceDataProviderTests.java index 14b0205b..808574a6 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/LabeledSecretContextToSourceDataProviderTests.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/LabeledSecretContextToSourceDataProviderTests.java @@ -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 properties = sourceData.sourceData(); + Assertions.assertEquals(2, properties.size()); + Iterator 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"); + } + } diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/NamedConfigMapContextToSourceDataProviderTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/NamedConfigMapContextToSourceDataProviderTests.java index d3e28fb8..891eab41 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/NamedConfigMapContextToSourceDataProviderTests.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/NamedConfigMapContextToSourceDataProviderTests.java @@ -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()); diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/NamedSecretContextToSourceDataProviderTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/NamedSecretContextToSourceDataProviderTests.java index aca03080..cff9117e 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/NamedSecretContextToSourceDataProviderTests.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/NamedSecretContextToSourceDataProviderTests.java @@ -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()); diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/LabeledSecretWithPrefixApp.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/LabeledSecretWithPrefixApp.java new file mode 100644 index 00000000..28484bcc --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/LabeledSecretWithPrefixApp.java @@ -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); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/LabeledSecretWithPrefixBootstrapTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/LabeledSecretWithPrefixBootstrapTests.java new file mode 100644 index 00000000..0987907d --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/LabeledSecretWithPrefixBootstrapTests.java @@ -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 { + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/LabeledSecretWithPrefixConfigDataTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/LabeledSecretWithPrefixConfigDataTests.java new file mode 100644 index 00000000..af37c9ac --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/LabeledSecretWithPrefixConfigDataTests.java @@ -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 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(); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/LabeledSecretWithPrefixTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/LabeledSecretWithPrefixTests.java new file mode 100644 index 00000000..6300f24f --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/LabeledSecretWithPrefixTests.java @@ -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(); + } + + /** + *
+	 *   'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.secrets.sources[0].useNameAsPrefix=false'
+	 * 	 ("one.property", "one")
+	 *
+	 * 	 As such: @ConfigurationProperties("one")
+	 * 
+ */ + @Test + void testOne() { + this.webClient.get().uri("/labeled-secret/prefix/one").exchange().expectStatus().isOk().expectBody(String.class) + .value(Matchers.equalTo("one")); + } + + /** + *
+	 *   'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.secrets.sources[1].explicitPrefix=two'
+	 * 	 ("property", "two")
+	 *
+	 * 	 As such: @ConfigurationProperties("two")
+	 * 
+ */ + @Test + void testTwo() { + this.webClient.get().uri("/labeled-secret/prefix/two").exchange().expectStatus().isOk().expectBody(String.class) + .value(Matchers.equalTo("two")); + } + + /** + *
+	 *   '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")
+	 * 
+ */ + @Test + void testThree() { + this.webClient.get().uri("/labeled-secret/prefix/three").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("three")); + } + + /** + *
+	 *   '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")
+	 * 
+ */ + @Test + void testFour() { + this.webClient.get().uri("/labeled-secret/prefix/four").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("four")); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/controller/LabeledSecretWithPrefixController.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/controller/LabeledSecretWithPrefixController.java new file mode 100644 index 00000000..b6c6066b --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/controller/LabeledSecretWithPrefixController.java @@ -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(); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/properties/Four.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/properties/Four.java new file mode 100644 index 00000000..aefd225d --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/properties/Four.java @@ -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; + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/properties/One.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/properties/One.java new file mode 100644 index 00000000..6ad8b434 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/properties/One.java @@ -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; + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/properties/Three.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/properties/Three.java new file mode 100644 index 00000000..ca661c7c --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/properties/Three.java @@ -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; + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/properties/Two.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/properties/Two.java new file mode 100644 index 00000000..265b17e5 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/properties/Two.java @@ -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; + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_prefix/NamedSecretWithPrefixBootstrapTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_prefix/NamedSecretWithPrefixBootstrapTests.java index 8dfdde9e..9c11d631 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_prefix/NamedSecretWithPrefixBootstrapTests.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_prefix/NamedSecretWithPrefixBootstrapTests.java @@ -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 { } diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_prefix/NamedSecretWithPrefixConfigDataTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_prefix/NamedSecretWithPrefixConfigDataTests.java index cc2050d0..7022d414 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_prefix/NamedSecretWithPrefixConfigDataTests.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_prefix/NamedSecretWithPrefixConfigDataTests.java @@ -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 clientUtilsMock; diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/LabeledSecretWithPrefixConfigurationStub.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/LabeledSecretWithPrefixConfigurationStub.java new file mode 100644 index 00000000..d795c0be --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/LabeledSecretWithPrefixConfigurationStub.java @@ -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)))); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/resources/META-INF/spring.factories b/spring-cloud-kubernetes-client-config/src/test/resources/META-INF/spring.factories index 92ca9beb..9a1bd646 100644 --- a/spring-cloud-kubernetes-client-config/src/test/resources/META-INF/spring.factories +++ b/spring-cloud-kubernetes-client-config/src/test/resources/META-INF/spring.factories @@ -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 diff --git a/spring-cloud-kubernetes-client-config/src/test/resources/labeled-secret-with-prefix.yaml b/spring-cloud-kubernetes-client-config/src/test/resources/labeled-secret-with-prefix.yaml new file mode 100644 index 00000000..fa6cb72d --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/resources/labeled-secret-with-prefix.yaml @@ -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 diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ConfigMapConfigProperties.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ConfigMapConfigProperties.java index edfec428..9665e874 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ConfigMapConfigProperties.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ConfigMapConfigProperties.java @@ -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, diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ConfigUtils.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ConfigUtils.java index a6cc43ba..f28f6880 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ConfigUtils.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ConfigUtils.java @@ -17,6 +17,7 @@ package org.springframework.cloud.kubernetes.commons.config; import java.util.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 prefixProvider() { + return prefixProvider; + } + + private final Supplier prefixProvider; + + private Prefix(Supplier prefixProvider) { + this.prefixProvider = prefixProvider; + } + + private static void computeKnown(Supplier supplier) { + KNOWN = new Prefix(supplier); + } + + } + } diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/LabeledSecretNormalizedSource.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/LabeledSecretNormalizedSource.java index 5b7e1d85..4f32097c 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/LabeledSecretNormalizedSource.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/LabeledSecretNormalizedSource.java @@ -29,9 +29,19 @@ public final class LabeledSecretNormalizedSource extends NormalizedSource { private final Map labels; + private final ConfigUtils.Prefix prefix; + + public LabeledSecretNormalizedSource(String namespace, Map 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 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; diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NamedConfigMapNormalizedSource.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NamedConfigMapNormalizedSource.java index 417a67b2..3d6b6085 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NamedConfigMapNormalizedSource.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NamedConfigMapNormalizedSource.java @@ -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; } diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NamedSecretNormalizedSource.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NamedSecretNormalizedSource.java index 619196f6..690b0cbd 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NamedSecretNormalizedSource.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NamedSecretNormalizedSource.java @@ -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; } diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/SecretsConfigProperties.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/SecretsConfigProperties.java index 80743835..f9cd729c 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/SecretsConfigProperties.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/SecretsConfigProperties.java @@ -96,10 +96,11 @@ public class SecretsConfigProperties extends AbstractConfigProperties { if (this.sources.isEmpty()) { List 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(); diff --git a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/ConfigMapConfigPropertiesTests.java b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/ConfigMapConfigPropertiesTests.java index 70defb44..45d17f9e 100644 --- a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/ConfigMapConfigPropertiesTests.java +++ b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/ConfigMapConfigPropertiesTests.java @@ -52,7 +52,7 @@ class ConfigMapConfigPropertiesTests { List 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 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 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 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 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); } /** diff --git a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/ConfigUtilsTests.java b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/ConfigUtilsTests.java index 4db1accd..c7843dee 100644 --- a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/ConfigUtilsTests.java +++ b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/ConfigUtilsTests.java @@ -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()); } /** diff --git a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/LabeledSecretNormalizedSourceTests.java b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/LabeledSecretNormalizedSourceTests.java index d7bb2a72..7b060320 100644 --- a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/LabeledSecretNormalizedSourceTests.java +++ b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/LabeledSecretNormalizedSourceTests.java @@ -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); } } diff --git a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/NamedConfigMapNormalizedSourceTests.java b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/NamedConfigMapNormalizedSourceTests.java index a13e77d2..0cb9bb05 100644 --- a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/NamedConfigMapNormalizedSourceTests.java +++ b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/NamedConfigMapNormalizedSourceTests.java @@ -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"); diff --git a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/NamedSecretNormalizedSourceTests.java b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/NamedSecretNormalizedSourceTests.java index da99eccc..ee93ac57 100644 --- a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/NamedSecretNormalizedSourceTests.java +++ b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/NamedSecretNormalizedSourceTests.java @@ -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()); } } diff --git a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/SecretsConfigPropertiesTests.java b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/SecretsConfigPropertiesTests.java index 91094d58..194ea83a 100644 --- a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/SecretsConfigPropertiesTests.java +++ b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/SecretsConfigPropertiesTests.java @@ -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 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 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 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 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 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); + } + + /** + *
+	 * 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
+	 * 
+ * + */ + @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 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 set = new LinkedHashSet<>(sources); + Assertions.assertEquals(5, set.size()); } } diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesConfigServerAutoConfiguration.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesConfigServerAutoConfiguration.java index 6d6d68a7..e8ef2982 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesConfigServerAutoConfiguration.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesConfigServerAutoConfiguration.java @@ -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 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)); diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/KubernetesEnvironmentRepositoryTests.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/KubernetesEnvironmentRepositoryTests.java index 3728853e..ca297b49 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/KubernetesEnvironmentRepositoryTests.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/KubernetesEnvironmentRepositoryTests.java @@ -101,12 +101,12 @@ class KubernetesEnvironmentRepositoryTests { kubernetesPropertySourceSuppliers.add((coreApi, applicationName, namespace, springEnv) -> { List 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 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); diff --git a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/LabeledSecretContextToSourceDataProvider.java b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/LabeledSecretContextToSourceDataProvider.java index c0790415..b927a918 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/LabeledSecretContextToSourceDataProvider.java +++ b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/LabeledSecretContextToSourceDataProvider.java @@ -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 { LabeledSecretNormalizedSource source = ((LabeledSecretNormalizedSource) context.normalizedSource()); + Set propertySourceNames = new LinkedHashSet<>(); Map labels = source.labels(); Map 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 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(fabric8ConfigMapPropertySource)); diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigMapPropertySourceLocatorTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigMapPropertySourceLocatorTests.java index b359c9c5..adcbf26c 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigMapPropertySourceLocatorTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigMapPropertySourceLocatorTests.java @@ -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); } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigMapPropertySourceTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigMapPropertySourceTests.java index b9299a7d..c2aa4d35 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigMapPropertySourceTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigMapPropertySourceTests.java @@ -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(); } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8SecretsPropertySourceMockTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8SecretsPropertySourceMockTests.java index 59c86540..da916d1a 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8SecretsPropertySourceMockTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8SecretsPropertySourceMockTests.java @@ -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(); diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/LabeledSecretContextToSourceDataProviderTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/LabeledSecretContextToSourceDataProviderTests.java index d0a63542..88db0bda 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/LabeledSecretContextToSourceDataProviderTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/LabeledSecretContextToSourceDataProviderTests.java @@ -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 properties = sourceData.sourceData(); + Assertions.assertEquals(2, properties.size()); + Iterator 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"); + } + } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/NamedConfigMapContextToSourceDataProviderTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/NamedConfigMapContextToSourceDataProviderTests.java index 0e00cd23..6e21f425 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/NamedConfigMapContextToSourceDataProviderTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/NamedConfigMapContextToSourceDataProviderTests.java @@ -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()); diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/NamedSecretContextToSourceDataProviderTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/NamedSecretContextToSourceDataProviderTests.java index 7bc7e731..75fc21a1 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/NamedSecretContextToSourceDataProviderTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/NamedSecretContextToSourceDataProviderTests.java @@ -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()); diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/LabeledSecretWithPrefixApp.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/LabeledSecretWithPrefixApp.java new file mode 100644 index 00000000..fa71ec9c --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/LabeledSecretWithPrefixApp.java @@ -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); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/LabeledSecretWithPrefixBootstrapTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/LabeledSecretWithPrefixBootstrapTests.java new file mode 100644 index 00000000..5567b604 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/LabeledSecretWithPrefixBootstrapTests.java @@ -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); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/LabeledSecretWithPrefixConfigDataTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/LabeledSecretWithPrefixConfigDataTests.java new file mode 100644 index 00000000..e9d120c9 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/LabeledSecretWithPrefixConfigDataTests.java @@ -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); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/LabeledSecretWithPrefixTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/LabeledSecretWithPrefixTests.java new file mode 100644 index 00000000..a43164f4 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/LabeledSecretWithPrefixTests.java @@ -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 one = Collections.singletonMap("one.property", + Base64.getEncoder().encodeToString("one".getBytes(StandardCharsets.UTF_8))); + + createSecret("secret-one", one, Collections.singletonMap("letter", "a")); + + Map two = Collections.singletonMap("property", + Base64.getEncoder().encodeToString("two".getBytes(StandardCharsets.UTF_8))); + createSecret("secret-two", two, Collections.singletonMap("letter", "b")); + + Map three = Collections.singletonMap("property", + Base64.getEncoder().encodeToString("three".getBytes(StandardCharsets.UTF_8))); + createSecret("secret-three", three, Collections.singletonMap("letter", "c")); + + Map 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 data, Map labels) { + mockClient.secrets().inNamespace("spring-k8s").create(new SecretBuilder().withNewMetadata().withName(name) + .withLabels(labels).endMetadata().addToData(data).build()); + } + + /** + *
+	 *   'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.secrets.sources[0].useNameAsPrefix=false'
+	 * 	 ("one.property", "one")
+	 *
+	 * 	 As such: @ConfigurationProperties("one")
+	 * 
+ */ + @Test + void testOne() { + this.webClient.get().uri("/labeled-secret/prefix/one").exchange().expectStatus().isOk().expectBody(String.class) + .value(Matchers.equalTo("one")); + } + + /** + *
+	 *   'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.secrets.sources[1].explicitPrefix=two'
+	 * 	 ("property", "two")
+	 *
+	 * 	 As such: @ConfigurationProperties("two")
+	 * 
+ */ + @Test + void testTwo() { + this.webClient.get().uri("/labeled-secret/prefix/two").exchange().expectStatus().isOk().expectBody(String.class) + .value(Matchers.equalTo("two")); + } + + /** + *
+	 *   '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")
+	 * 
+ */ + @Test + void testThree() { + this.webClient.get().uri("/labeled-secret/prefix/three").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("three")); + } + + /** + *
+	 *   '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")
+	 * 
+ */ + @Test + void testFour() { + this.webClient.get().uri("/labeled-secret/prefix/four").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("four")); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/controller/LabeledSecretWithPrefixController.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/controller/LabeledSecretWithPrefixController.java new file mode 100644 index 00000000..ff10bfe3 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/controller/LabeledSecretWithPrefixController.java @@ -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(); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/properties/Four.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/properties/Four.java new file mode 100644 index 00000000..536b2744 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/properties/Four.java @@ -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; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/properties/One.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/properties/One.java new file mode 100644 index 00000000..f14774f4 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/properties/One.java @@ -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; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/properties/Three.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/properties/Three.java new file mode 100644 index 00000000..251f0f3e --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/properties/Three.java @@ -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; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/properties/Two.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/properties/Two.java new file mode 100644 index 00000000..a4a70d2c --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/properties/Two.java @@ -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; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/resources/labeled-secret-with-prefix.yaml b/spring-cloud-kubernetes-fabric8-config/src/test/resources/labeled-secret-with-prefix.yaml new file mode 100644 index 00000000..fa6cb72d --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/resources/labeled-secret-with-prefix.yaml @@ -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