diff --git a/docs/src/main/asciidoc/property-source-config.adoc b/docs/src/main/asciidoc/property-source-config.adoc index 1ccff78c..034af722 100644 --- a/docs/src/main/asciidoc/property-source-config.adoc +++ b/docs/src/main/asciidoc/property-source-config.adoc @@ -46,6 +46,9 @@ spring: In the preceding example, if `spring.cloud.kubernetes.config.namespace` had not been set, the `ConfigMap` named `c1` would be looked up in the namespace that the application runs. +See <> to get a better understanding of how the namespace +of the application is resolved. + Any matching `ConfigMap` that is found is processed as follows: @@ -562,7 +565,7 @@ spring: sources: # Spring Cloud Kubernetes looks up a Secret named s1 in namespace default-namespace - name: s1 - # Spring Cloud Kubernetes looks up a Secret named default-name in whatever namespace n2 + # Spring Cloud Kubernetes looks up a Secret named default-name in namespace n2 - namespace: n2 # Spring Cloud Kubernetes looks up a Secret named s3 in namespace n3 - namespace: n3 @@ -572,6 +575,8 @@ spring: In the preceding example, if `spring.cloud.kubernetes.secrets.namespace` had not been set, the `Secret` named `s1` would be looked up in the namespace that the application runs. +See <> to get a better understanding of how the namespace +of the application is resolved. .Properties: @@ -597,6 +602,44 @@ https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-Configuration-Bi You can find an example of an application that uses secrets (though it has not been updated to use the new `spring-cloud-kubernetes` project) at https://github.com/fabric8-quickstarts/spring-boot-camel-config[spring-boot-camel-config] +[[namespace-resolution]] +=== Namespace resolution +Finding an application namespace happens on a best-effort basis. There are some steps that we iterate in order +to find it. The easiest and most common one, is to specify it in the proper configuration, for example: + +==== +[source,yaml] +---- +spring: + application: + name: app + cloud: + kubernetes: + secrets: + name: secret + namespace: default + sources: + # Spring Cloud Kubernetes looks up a Secret named 'a' in namespace 'default' + - name: a + # Spring Cloud Kubernetes looks up a Secret named 'secret' in namespace 'b' + - namespace: b + # Spring Cloud Kubernetes looks up a Secret named 'd' in namespace 'c' + - namespace: c + name: d +---- +==== + +Remember that the same can be done for config maps. If such a namespace is not specified, it will be read (in this order): + +1. from property `spring.cloud.kubernetes.client.namespace` +2. from a String residing in a file denoted by `spring.cloud.kubernetes.client.serviceAccountNamespacePath` property +3. from a String residing in `/var/run/secrets/kubernetes.io/serviceaccount/namespace` file +(kubernetes default namespace path) +4. from a designated client method call (for example fabric8's : `KubernetesClient::getNamespace`), if the client provides +such a method. + +Failure to find a namespace from the above steps will result in an Exception being raised. + === `PropertySource` Reload WARNING: This functionality has been deprecated in the 2020.0 release. Please see diff --git a/spring-cloud-kubernetes-client-config/pom.xml b/spring-cloud-kubernetes-client-config/pom.xml index a4bdbf0a..1b250133 100644 --- a/spring-cloud-kubernetes-client-config/pom.xml +++ b/spring-cloud-kubernetes-client-config/pom.xml @@ -74,6 +74,12 @@ test + + org.springframework.boot + spring-boot-starter-web + test + + com.github.tomakehurst wiremock-jre8 diff --git a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapPropertySourceLocator.java b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapPropertySourceLocator.java index 043dc7c3..245c63b4 100644 --- a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapPropertySourceLocator.java +++ b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapPropertySourceLocator.java @@ -22,10 +22,10 @@ import org.springframework.cloud.kubernetes.commons.KubernetesClientProperties; import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider; import org.springframework.cloud.kubernetes.commons.config.ConfigMapConfigProperties; import org.springframework.cloud.kubernetes.commons.config.ConfigMapPropertySourceLocator; +import org.springframework.cloud.kubernetes.commons.config.NamespaceResolutionFailedException; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; - -import static org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigUtils.getNamespace; +import org.springframework.util.StringUtils; /** * @author Ryan Baxter @@ -38,6 +38,14 @@ public class KubernetesClientConfigMapPropertySourceLocator extends ConfigMapPro private final KubernetesNamespaceProvider kubernetesNamespaceProvider; + /** + * This constructor is deprecated. Its usage might cause unexpected behavior when + * looking for different properties. For example, in general, if a namespace is not + * provided, we might look it up via other means: different documented environment + * variables or from a kubernetes client itself. Using this constructor might not + * reflect that. + */ + @Deprecated public KubernetesClientConfigMapPropertySourceLocator(CoreV1Api coreV1Api, ConfigMapConfigProperties properties, KubernetesClientProperties kubernetesClientProperties) { super(properties); @@ -59,10 +67,28 @@ public class KubernetesClientConfigMapPropertySourceLocator extends ConfigMapPro ConfigMapConfigProperties.NormalizedSource normalizedSource, String configurationTarget, ConfigurableEnvironment environment) { - String fallbackNamespace = kubernetesNamespaceProvider != null ? kubernetesNamespaceProvider.getNamespace() - : kubernetesClientProperties.getNamespace(); - return new KubernetesClientConfigMapPropertySource(coreV1Api, name, - getNamespace(normalizedSource, fallbackNamespace), environment, normalizedSource.getPrefix()); + String namespace; + String normalizedNamespace = normalizedSource.getNamespace(); + + if (StringUtils.hasText(normalizedNamespace)) { + namespace = normalizedNamespace; + } + else if (kubernetesClientProperties != null) { + if (StringUtils.hasText(kubernetesClientProperties.getNamespace())) { + namespace = kubernetesClientProperties.getNamespace(); + } + else { + throw new NamespaceResolutionFailedException( + "could not resolve namespace in normalized source or KubernetesClientProperties"); + } + } + else { + namespace = KubernetesClientConfigUtils.getApplicationNamespace(normalizedNamespace, "Config Map", + kubernetesNamespaceProvider); + } + + return new KubernetesClientConfigMapPropertySource(coreV1Api, name, namespace, environment, + normalizedSource.getPrefix()); } } diff --git a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigUtils.java b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigUtils.java index 4f96505a..56ff6cea 100644 --- a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigUtils.java +++ b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigUtils.java @@ -16,8 +16,13 @@ package org.springframework.cloud.kubernetes.client.config; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import org.springframework.cloud.kubernetes.commons.KubernetesClientProperties; +import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider; import org.springframework.cloud.kubernetes.commons.config.ConfigMapConfigProperties; +import org.springframework.cloud.kubernetes.commons.config.NamespaceResolutionFailedException; import org.springframework.cloud.kubernetes.commons.config.SecretsConfigProperties; import org.springframework.util.StringUtils; @@ -26,6 +31,8 @@ import org.springframework.util.StringUtils; */ public final class KubernetesClientConfigUtils { + private static final Log LOG = LogFactory.getLog(KubernetesClientConfigUtils.class); + private KubernetesClientConfigUtils() { } @@ -51,16 +58,56 @@ public final class KubernetesClientConfigUtils { } } + @Deprecated public static String getNamespace(ConfigMapConfigProperties.NormalizedSource normalizedSource, String fallbackNamespace) { String normalizedNamespace = normalizedSource.getNamespace(); return StringUtils.hasText(normalizedNamespace) ? normalizedNamespace : fallbackNamespace; } + @Deprecated public static String getNamespace(SecretsConfigProperties.NormalizedSource normalizedSource, String fallbackNamespace) { String normalizedNamespace = normalizedSource.getNamespace(); return StringUtils.hasText(normalizedNamespace) ? normalizedNamespace : fallbackNamespace; } + /** + * this method does the namespace resolution for both config map and secrets + * implementations. It tries these places to find the namespace: + * + *
+	 *     1. from a normalized source (which can be null)
+	 *     2. from a property 'spring.cloud.kubernetes.client.namespace', if such is present
+	 *     3. from a String residing in a file denoted by `spring.cloud.kubernetes.client.serviceAccountNamespacePath`
+	 * 	      property, if such is present
+	 * 	   4. from a String residing in `/var/run/secrets/kubernetes.io/serviceaccount/namespace` file,
+	 * 	  	  if such is present (kubernetes default path)
+	 * 
+ * + * If any of the above fail, we throw a NamespaceResolutionFailedException. + * @param namespace normalized namespace + * @param configurationTarget Config Map/Secret + * @param provider the provider which computes the namespace + * @return application namespace + * @throws NamespaceResolutionFailedException when namespace could not be resolved + */ + static String getApplicationNamespace(String namespace, String configurationTarget, + KubernetesNamespaceProvider provider) { + if (StringUtils.hasText(namespace)) { + LOG.debug(configurationTarget + " namespace from normalized source : " + namespace); + return namespace; + } + + if (provider != null) { + String providerNamespace = provider.getNamespace(); + if (StringUtils.hasText(providerNamespace)) { + LOG.debug(configurationTarget + " namespace from provider : " + namespace); + return providerNamespace; + } + } + + throw new NamespaceResolutionFailedException("unresolved namespace"); + } + } diff --git a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientSecretsPropertySourceLocator.java b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientSecretsPropertySourceLocator.java index c463fc00..c1b296ba 100644 --- a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientSecretsPropertySourceLocator.java +++ b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientSecretsPropertySourceLocator.java @@ -20,12 +20,13 @@ import io.kubernetes.client.openapi.apis.CoreV1Api; import org.springframework.cloud.kubernetes.commons.KubernetesClientProperties; import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider; +import org.springframework.cloud.kubernetes.commons.config.NamespaceResolutionFailedException; import org.springframework.cloud.kubernetes.commons.config.SecretsConfigProperties; import org.springframework.cloud.kubernetes.commons.config.SecretsPropertySourceLocator; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; +import org.springframework.util.StringUtils; -import static org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigUtils.getNamespace; import static org.springframework.cloud.kubernetes.commons.config.ConfigUtils.getApplicationName; /** @@ -66,11 +67,30 @@ public class KubernetesClientSecretsPropertySourceLocator extends SecretsPropert @Override protected MapPropertySource getPropertySource(ConfigurableEnvironment environment, SecretsConfigProperties.NormalizedSource normalizedSource, String configurationTarget) { - String fallbackNamespace = kubernetesNamespaceProvider != null ? kubernetesNamespaceProvider.getNamespace() - : kubernetesClientProperties.getNamespace(); - return new KubernetesClientSecretsPropertySource(coreV1Api, - getApplicationName(environment, normalizedSource.getName(), configurationTarget), - getNamespace(normalizedSource, fallbackNamespace), environment, normalizedSource.getLabels()); + + String namespace; + String normalizedNamespace = normalizedSource.getNamespace(); + String secretName = getApplicationName(environment, normalizedSource.getName(), configurationTarget); + + if (StringUtils.hasText(normalizedNamespace)) { + namespace = normalizedNamespace; + } + else if (kubernetesClientProperties != null) { + if (StringUtils.hasText(kubernetesClientProperties.getNamespace())) { + namespace = kubernetesClientProperties.getNamespace(); + } + else { + throw new NamespaceResolutionFailedException( + "could not resolve namespace in normalized source or KubernetesClientProperties"); + } + } + else { + namespace = KubernetesClientConfigUtils.getApplicationNamespace(normalizedNamespace, "Secret", + kubernetesNamespaceProvider); + } + + return new KubernetesClientSecretsPropertySource(coreV1Api, secretName, namespace, environment, + normalizedSource.getLabels()); } } diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientBootstrapConfigurationTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientBootstrapConfigurationTests.java new file mode 100644 index 00000000..72680a0f --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientBootstrapConfigurationTests.java @@ -0,0 +1,138 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ConfigurableApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author wind57 + */ +public class KubernetesClientBootstrapConfigurationTests { + + @SpringBootApplication + static class Application { + + } + + @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, + properties = "spring.cloud.kubernetes.enabled=false") + @Nested + class KubernetesDisabled { + + @Autowired + ConfigurableApplicationContext context; + + @Test + void configAndSecretsBeansAreNotPresent() { + assertThat(context.getBeanNamesForType(KubernetesClientConfigMapPropertySourceLocator.class)).hasSize(0); + assertThat(context.getBeanNamesForType(KubernetesClientSecretsPropertySourceLocator.class)).hasSize(0); + } + + } + + @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, + properties = { "spring.cloud.kubernetes.secrets.enabled=true", + "spring.cloud.kubernetes.client.namespace=default" }) + @Nested + class KubernetesEnabledOnPurpose { + + @Autowired + ConfigurableApplicationContext context; + + @Test + void configAndSecretsBeansArePresent() { + assertThat(context.getBeanNamesForType(KubernetesClientConfigMapPropertySourceLocator.class)).hasSize(1); + assertThat(context.getBeanNamesForType(KubernetesClientSecretsPropertySourceLocator.class)).hasSize(1); + } + + } + + @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, + properties = "spring.cloud.kubernetes.client.namespace=default") + @Nested + class KubernetesEnabled { + + @Autowired + ConfigurableApplicationContext context; + + @Test + void configAndSecretsBeansArePresent() { + assertThat(context.getBeanNamesForType(KubernetesClientConfigMapPropertySourceLocator.class)).hasSize(1); + assertThat(context.getBeanNamesForType(KubernetesClientSecretsPropertySourceLocator.class)).hasSize(1); + } + + } + + @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, + properties = "spring.cloud.kubernetes.config.enabled=false") + @Nested + class KubernetesEnabledConfigDisabled { + + @Autowired + ConfigurableApplicationContext context; + + @Test + void secretsOnlyPresent() { + assertThat(context.getBeanNamesForType(KubernetesClientConfigMapPropertySourceLocator.class)).hasSize(0); + assertThat(context.getBeanNamesForType(KubernetesClientSecretsPropertySourceLocator.class)).hasSize(1); + } + + } + + @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, + properties = { "spring.cloud.kubernetes.secrets.enabled=false", + "spring.cloud.kubernetes.client.namespace=default" }) + @Nested + class KubernetesEnabledSecretsDisabled { + + @Autowired + ConfigurableApplicationContext context; + + @Test + void secretsOnlyPresent() { + assertThat(context.getBeanNamesForType(KubernetesClientConfigMapPropertySourceLocator.class)).hasSize(1); + assertThat(context.getBeanNamesForType(KubernetesClientSecretsPropertySourceLocator.class)).hasSize(0); + } + + } + + @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, + properties = { "spring.cloud.kubernetes.secrets.enabled=false", + "spring.cloud.kubernetes.config.enabled=false" }) + @Nested + class KubernetesEnabledSecretsAndConfigDisabled { + + @Autowired + ConfigurableApplicationContext context; + + @Test + void secretsOnlyPresent() { + assertThat(context.getBeanNamesForType(KubernetesClientConfigMapPropertySourceLocator.class)).hasSize(0); + assertThat(context.getBeanNamesForType(KubernetesClientSecretsPropertySourceLocator.class)).hasSize(0); + } + + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapPropertySourceLocatorTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapPropertySourceLocatorTests.java index b4e6031d..4cc15ebe 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapPropertySourceLocatorTests.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapPropertySourceLocatorTests.java @@ -35,7 +35,9 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.springframework.cloud.kubernetes.commons.KubernetesClientProperties; +import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider; import org.springframework.cloud.kubernetes.commons.config.ConfigMapConfigProperties; +import org.springframework.cloud.kubernetes.commons.config.NamespaceResolutionFailedException; import org.springframework.core.env.PropertySource; import org.springframework.mock.env.MockEnvironment; @@ -44,6 +46,7 @@ import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; /** * @author Ryan Baxter @@ -121,4 +124,48 @@ class KubernetesClientConfigMapPropertySourceLocatorTests { .isTrue(); } + /** + *
+	 *     1. using the deprecated constructor, and
+	 *     2. not providing the namespace
+	 * 
+ * + * will result in an Exception + */ + @Test + void testLocateWithoutNamespaceDeprecatedConstructor() { + CoreV1Api api = new CoreV1Api(); + stubFor(get(API) + .willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(PROPERTIES_CONFIGMAP_LIST)))); + ConfigMapConfigProperties configMapConfigProperties = new ConfigMapConfigProperties(); + configMapConfigProperties.setName("bootstrap-640"); + KubernetesClientProperties kubernetesClientProperties = new KubernetesClientProperties(); + kubernetesClientProperties.setNamespace(""); // empty on purpose + assertThatThrownBy(() -> new KubernetesClientConfigMapPropertySourceLocator(api, configMapConfigProperties, + kubernetesClientProperties).locate(new MockEnvironment())) + .isInstanceOf(NamespaceResolutionFailedException.class); + } + + /** + *
+	 *     1. using the non-deprecated constructor, and
+	 *     2. not providing the namespace
+	 * 
+ * + * will result in an Exception + */ + @Test + void testLocateWithoutNamespace() { + CoreV1Api api = new CoreV1Api(); + stubFor(get(API) + .willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(PROPERTIES_CONFIGMAP_LIST)))); + ConfigMapConfigProperties configMapConfigProperties = new ConfigMapConfigProperties(); + configMapConfigProperties.setName("bootstrap-640"); + KubernetesClientProperties kubernetesClientProperties = new KubernetesClientProperties(); + kubernetesClientProperties.setNamespace(""); // empty on purpose + assertThatThrownBy(() -> new KubernetesClientConfigMapPropertySourceLocator(api, configMapConfigProperties, + new KubernetesNamespaceProvider(new MockEnvironment())).locate(new MockEnvironment())) + .isInstanceOf(NamespaceResolutionFailedException.class); + } + } diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigUtilsTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigUtilsTests.java new file mode 100644 index 00000000..aede9f0b --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigUtilsTests.java @@ -0,0 +1,54 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config; + +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider; +import org.springframework.cloud.kubernetes.commons.config.NamespaceResolutionFailedException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * @author wind57 + */ +class KubernetesClientConfigUtilsTests { + + private final KubernetesNamespaceProvider provider = Mockito.mock(KubernetesNamespaceProvider.class); + + @Test + void testNamespaceFromNormalizedSource() { + String result = KubernetesClientConfigUtils.getApplicationNamespace("abc", "target", null); + assertThat(result).isEqualTo("abc"); + } + + @Test + void testNamespaceFromProvider() { + Mockito.when(provider.getNamespace()).thenReturn("def"); + String result = KubernetesClientConfigUtils.getApplicationNamespace("", "target", provider); + assertThat(result).isEqualTo("def"); + } + + @Test + void testNamespaceResolutionFailed() { + assertThatThrownBy(() -> KubernetesClientConfigUtils.getApplicationNamespace("", "target", null)) + .isInstanceOf(NamespaceResolutionFailedException.class); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientSecretsPropertySourceLocatorTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientSecretsPropertySourceLocatorTests.java index 4c7eb5b8..d65cd2a2 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientSecretsPropertySourceLocatorTests.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientSecretsPropertySourceLocatorTests.java @@ -31,6 +31,8 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.springframework.cloud.kubernetes.commons.KubernetesClientProperties; +import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider; +import org.springframework.cloud.kubernetes.commons.config.NamespaceResolutionFailedException; import org.springframework.cloud.kubernetes.commons.config.SecretsConfigProperties; import org.springframework.core.env.PropertySource; import org.springframework.mock.env.MockEnvironment; @@ -40,15 +42,14 @@ import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; /** * @author Ryan Baxter */ class KubernetesClientSecretsPropertySourceLocatorTests { - private static final String LIST_API = "/api/v1/secrets"; - - private static final String LIST_API_WITH_LABEL = "/api/v1/secrets?labelSelector=spring.cloud.kubernetes.secret%3Dtrue"; + private static final String LIST_API = "/api/v1/namespaces/default/secrets"; private static final String LIST_BODY = "{\n" + "\t\"kind\": \"SecretList\",\n" + "\t\"apiVersion\": \"v1\",\n" + "\t\"metadata\": {\n" + "\t\t\"selfLink\": \"/api/v1/secrets\",\n" @@ -112,10 +113,10 @@ class KubernetesClientSecretsPropertySourceLocatorTests { sources.add(source1); sources.add(source2); secretsConfigProperties.setName("app"); - secretsConfigProperties.setNamespace(""); + secretsConfigProperties.setNamespace("default"); secretsConfigProperties.setSources(sources); secretsConfigProperties.setEnableApi(true); - PropertySource propertySource = new KubernetesClientSecretsPropertySourceLocator(api, + PropertySource propertySource = new KubernetesClientSecretsPropertySourceLocator(api, new KubernetesClientProperties(), secretsConfigProperties).locate(new MockEnvironment()); assertThat(propertySource.containsProperty("password")).isTrue(); assertThat(propertySource.getProperty("password")).isEqualTo("p455w0rd"); @@ -127,12 +128,54 @@ class KubernetesClientSecretsPropertySourceLocatorTests { stubFor(get(LIST_API).willReturn(aResponse().withStatus(200).withBody(LIST_BODY))); SecretsConfigProperties secretsConfigProperties = new SecretsConfigProperties(); secretsConfigProperties.setName("db-secret"); - secretsConfigProperties.setNamespace(""); + secretsConfigProperties.setNamespace("default"); secretsConfigProperties.setEnableApi(true); - PropertySource propertySource = new KubernetesClientSecretsPropertySourceLocator(api, + PropertySource propertySource = new KubernetesClientSecretsPropertySourceLocator(api, new KubernetesClientProperties(), secretsConfigProperties).locate(new MockEnvironment()); assertThat(propertySource.containsProperty("password")).isTrue(); assertThat(propertySource.getProperty("password")).isEqualTo("p455w0rd"); } + /** + *
+	 *     1. using the deprecated constructor, and
+	 *     2. not providing the namespace
+	 * 
+ * + * will result in an Exception + */ + @Test + void testLocateWithoutNamespaceDeprecatedConstructor() { + CoreV1Api api = new CoreV1Api(); + stubFor(get(LIST_API).willReturn(aResponse().withStatus(200).withBody(LIST_BODY))); + SecretsConfigProperties secretsConfigProperties = new SecretsConfigProperties(); + secretsConfigProperties.setName("db-secret"); + secretsConfigProperties.setNamespace(""); // empty on purpose + secretsConfigProperties.setEnableApi(true); + assertThatThrownBy(() -> new KubernetesClientSecretsPropertySourceLocator(api, new KubernetesClientProperties(), + secretsConfigProperties).locate(new MockEnvironment())) + .isInstanceOf(NamespaceResolutionFailedException.class); + } + + /** + *
+	 *     1. using the non-deprecated constructor, and
+	 *     2. not providing the namespace
+	 * 
+ * + * will result in an Exception + */ + @Test + void testLocateWithoutNamespace() { + CoreV1Api api = new CoreV1Api(); + stubFor(get(LIST_API).willReturn(aResponse().withStatus(200).withBody(LIST_BODY))); + SecretsConfigProperties secretsConfigProperties = new SecretsConfigProperties(); + secretsConfigProperties.setName("db-secret"); + secretsConfigProperties.setNamespace(""); // empty on purpose + secretsConfigProperties.setEnableApi(true); + assertThatThrownBy(() -> new KubernetesClientSecretsPropertySourceLocator(api, + new KubernetesNamespaceProvider(new MockEnvironment()), secretsConfigProperties) + .locate(new MockEnvironment())).isInstanceOf(NamespaceResolutionFailedException.class); + } + } diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NamespaceResolutionFailedException.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NamespaceResolutionFailedException.java new file mode 100644 index 00000000..c3235120 --- /dev/null +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NamespaceResolutionFailedException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.commons.config; + +/** + * @author wind57 + * + * Thrown when a namespace could not be resolved. Resolution of a namespace happens via + * user provided input, environment properties or directly from the underlying client, + * etc. + */ +public class NamespaceResolutionFailedException extends RuntimeException { + + public NamespaceResolutionFailedException(String message) { + super(message); + } + +} diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/SecretsPropertySource.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/SecretsPropertySource.java index acab95d4..02cefbdc 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/SecretsPropertySource.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/SecretsPropertySource.java @@ -29,14 +29,12 @@ import org.springframework.core.env.MapPropertySource; */ public class SecretsPropertySource extends MapPropertySource { - private static final String PREFIX = "secrets"; - public SecretsPropertySource(String name, Map source) { super(name, source); } protected static String getSourceName(String name, String namespace) { - return PREFIX + Constants.PROPERTY_SOURCE_NAME_SEPARATOR + name + Constants.PROPERTY_SOURCE_NAME_SEPARATOR + return "secrets" + Constants.PROPERTY_SOURCE_NAME_SEPARATOR + name + Constants.PROPERTY_SOURCE_NAME_SEPARATOR + namespace; } diff --git a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigMapPropertySource.java b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigMapPropertySource.java index 6874fbce..e53962b9 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigMapPropertySource.java +++ b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigMapPropertySource.java @@ -47,6 +47,11 @@ public class Fabric8ConfigMapPropertySource extends ConfigMapPropertySource { this(client, name, null, null, ""); } + /** + * this constructor is present only for compatibility reasons, its usage is + * discouraged. + */ + @Deprecated public Fabric8ConfigMapPropertySource(KubernetesClient client, String applicationName, String namespace, Environment environment) { super(getName(applicationName, getApplicationNamespace(client, namespace)), diff --git a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigMapPropertySourceLocator.java b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigMapPropertySourceLocator.java index be6b76d3..344f272a 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigMapPropertySourceLocator.java +++ b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigMapPropertySourceLocator.java @@ -66,9 +66,9 @@ public class Fabric8ConfigMapPropertySourceLocator extends ConfigMapPropertySour @Override protected MapPropertySource getMapPropertySource(String applicationName, NormalizedSource normalizedSource, String configurationTarget, ConfigurableEnvironment environment) { - String configMapName = getApplicationNamespace(this.client, normalizedSource.getNamespace(), - configurationTarget, provider); - return new Fabric8ConfigMapPropertySource(this.client, applicationName, configMapName, environment, + String namespace = getApplicationNamespace(this.client, normalizedSource.getNamespace(), configurationTarget, + provider); + return new Fabric8ConfigMapPropertySource(this.client, applicationName, namespace, environment, normalizedSource.getPrefix()); } diff --git a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigUtils.java b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigUtils.java index 000fb4f5..db4fe693 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigUtils.java +++ b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigUtils.java @@ -25,6 +25,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider; +import org.springframework.cloud.kubernetes.commons.config.NamespaceResolutionFailedException; import org.springframework.util.StringUtils; /** @@ -39,6 +40,9 @@ public final class Fabric8ConfigUtils { private Fabric8ConfigUtils() { } + /* + * this is not used, it is here for compatibility reasons only. + */ @Deprecated public static String getApplicationNamespace(KubernetesClient client, String namespace, String configurationTarget) { @@ -51,6 +55,28 @@ public final class Fabric8ConfigUtils { return namespace; } + /** + * this method does the namespace resolution for both config map and secrets + * implementations. It tries these places to find the namespace: + * + *
+	 *     1. from a normalized source (which can be null)
+	 *     2. from a property 'spring.cloud.kubernetes.client.namespace', if such is present
+	 *     3. from a String residing in a file denoted by `spring.cloud.kubernetes.client.serviceAccountNamespacePath`
+	 * 	      property, if such is present
+	 * 	   4. from a String residing in `/var/run/secrets/kubernetes.io/serviceaccount/namespace` file,
+	 * 	  	  if such is present (kubernetes default path)
+	 * 	   5. from KubernetesClient::getNamespace, which is implementation specific.
+	 * 
+ * + * If any of the above fail, we throw a NamespaceResolutionFailedException. + * @param namespace normalized namespace + * @param configurationTarget Config Map/Secret + * @param provider the provider which computes the namespace + * @param client fabric8 Kubernetes client + * @return application namespace + * @throws NamespaceResolutionFailedException when namespace could not be resolved + */ static String getApplicationNamespace(KubernetesClient client, String namespace, String configurationTarget, KubernetesNamespaceProvider provider) { @@ -63,11 +89,16 @@ public final class Fabric8ConfigUtils { String providerNamespace = provider.getNamespace(); if (StringUtils.hasText(providerNamespace)) { LOG.debug(configurationTarget + " namespace from provider : " + namespace); + return providerNamespace; } } - LOG.debug(configurationTarget + " namespace from client : " + client.getNamespace()); - return client.getNamespace(); + String clientNamespace = client.getNamespace(); + LOG.debug(configurationTarget + " namespace from client : " + clientNamespace); + if (clientNamespace == null) { + throw new NamespaceResolutionFailedException("unresolved namespace"); + } + return clientNamespace; } @@ -75,7 +106,7 @@ public final class Fabric8ConfigUtils { return !StringUtils.hasLength(namespace) ? client.getNamespace() : namespace; } - public static Map getConfigMapData(KubernetesClient client, String namespace, String name) { + static Map getConfigMapData(KubernetesClient client, String namespace, String name) { ConfigMap configMap = !StringUtils.hasLength(namespace) ? client.configMaps().withName(name).get() : client.configMaps().inNamespace(namespace).withName(name).get(); diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ActuatorTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ActuatorTests.java index d564d4c4..c0b86324 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ActuatorTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ActuatorTests.java @@ -39,7 +39,8 @@ public class Fabric8ActuatorTests { @Nested @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, properties = { "management.health.kubernetes.enabled=false", "management.endpoint.health.show-details=always", - "management.endpoint.health.show-components=always", "management.endpoints.web.exposure.include=health" }) + "management.endpoint.health.show-components=always", "management.endpoints.web.exposure.include=health", + "spring.cloud.kubernetes.client.namespace=default" }) public class DisabledHealthTest { @Autowired @@ -66,7 +67,8 @@ public class Fabric8ActuatorTests { @Nested @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, properties = { "management.health.kubernetes.enabled=true", "management.endpoint.health.show-details=always", - "management.endpoint.health.show-components=always", "management.endpoints.web.exposure.include=health" }) + "management.endpoint.health.show-components=always", "management.endpoints.web.exposure.include=health", + "spring.cloud.kubernetes.client.namespace=default" }) public class EnabledHealthTest { @Autowired diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8BootstrapConfigurationTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8BootstrapConfigurationTests.java index de96c45f..bf51180e 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8BootstrapConfigurationTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8BootstrapConfigurationTests.java @@ -47,7 +47,8 @@ public class Fabric8BootstrapConfigurationTests { } @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, - properties = "spring.cloud.kubernetes.enabled=true") + properties = { "spring.cloud.kubernetes.secrets.enabled=true", + "spring.cloud.kubernetes.client.namespace=default" }) @Nested class KubernetesEnabledOnPurpose { @@ -62,7 +63,8 @@ public class Fabric8BootstrapConfigurationTests { } - @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class) + @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, + properties = "spring.cloud.kubernetes.client.namespace=default") @Nested class KubernetesEnabled { @@ -94,7 +96,8 @@ public class Fabric8BootstrapConfigurationTests { } @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, - properties = "spring.cloud.kubernetes.secrets.enabled=false") + properties = { "spring.cloud.kubernetes.secrets.enabled=false", + "spring.cloud.kubernetes.client.namespace=default" }) @Nested class KubernetesEnabledSecretsDisabled { diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigUtilsTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigUtilsTests.java index 01ea8b6f..c1750ae3 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigUtilsTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigUtilsTests.java @@ -16,11 +16,17 @@ package org.springframework.cloud.kubernetes.fabric8.config; +import io.fabric8.kubernetes.client.DefaultKubernetesClient; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider; +import org.springframework.cloud.kubernetes.commons.config.NamespaceResolutionFailedException; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; /** * @author wind57 @@ -30,6 +36,10 @@ public class Fabric8ConfigUtilsTests { private KubernetesClient client; + private final DefaultKubernetesClient mockClient = Mockito.mock(DefaultKubernetesClient.class); + + private final KubernetesNamespaceProvider provider = Mockito.mock(KubernetesNamespaceProvider.class); + @Test public void testGetApplicationNamespaceNotPresent() { String result = Fabric8ConfigUtils.getApplicationNamespace(client, "", "target"); @@ -42,4 +52,30 @@ public class Fabric8ConfigUtilsTests { assertThat(result).isEqualTo("namespace"); } + @Test + void testNamespaceFromNormalizedSource() { + String result = Fabric8ConfigUtils.getApplicationNamespace(client, "abc", "target", null); + assertThat(result).isEqualTo("abc"); + } + + @Test + void testNamespaceFromProvider() { + Mockito.when(provider.getNamespace()).thenReturn("def"); + String result = Fabric8ConfigUtils.getApplicationNamespace(client, "", "target", provider); + assertThat(result).isEqualTo("def"); + } + + @Test + void testNamespaceFromClient() { + Mockito.when(mockClient.getNamespace()).thenReturn("qwe"); + String result = Fabric8ConfigUtils.getApplicationNamespace(mockClient, "", "target", null); + assertThat(result).isEqualTo("qwe"); + } + + @Test + void testNamespaceResolutionFailed() { + assertThatThrownBy(() -> Fabric8ConfigUtils.getApplicationNamespace(mockClient, "", "target", null)) + .isInstanceOf(NamespaceResolutionFailedException.class); + } + } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/KubernetesConfigConfigurationTest.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/KubernetesConfigConfigurationTest.java index 861c99db..0a05aa7b 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/KubernetesConfigConfigurationTest.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/KubernetesConfigConfigurationTest.java @@ -42,7 +42,8 @@ public class KubernetesConfigConfigurationTest extends KubernetesConfigTestBase @Test public void kubernetesWhenKubernetesDefaultEnabled() { - setup(KubernetesClientTestConfiguration.class, "spring.cloud.kubernetes.enabled=true"); + setup(KubernetesClientTestConfiguration.class, "spring.cloud.kubernetes.enabled=true", + "spring.cloud.kubernetes.client.namespace=default"); assertThat(getContext().containsBean("configMapPropertySourceLocator")).isTrue(); assertThat(getContext().containsBean("secretsPropertySourceLocator")).isTrue(); } @@ -65,7 +66,7 @@ public class KubernetesConfigConfigurationTest extends KubernetesConfigTestBase @Test public void kubernetesWhenKubernetesConfigEnabledButSecretDisabled() { setup(KubernetesClientTestConfiguration.class, "spring.cloud.kubernetes.config.enabled=true", - "spring.cloud.kubernetes.secrets.enabled=false"); + "spring.cloud.kubernetes.secrets.enabled=false", "spring.cloud.kubernetes.client.namespace=default"); assertThat(getContext().containsBean("configMapPropertySourceLocator")).isTrue(); assertThat(getContext().containsBean("secretsPropertySourceLocator")).isFalse(); } @@ -81,7 +82,7 @@ public class KubernetesConfigConfigurationTest extends KubernetesConfigTestBase @Test public void kubernetesConfigWhenKubernetesEnabledAndKubernetesConfigEnabled() { setup(KubernetesClientTestConfiguration.class, "spring.cloud.kubernetes.config.enabled=true", - "spring.cloud.kubernetes.secrets.enabled=true"); + "spring.cloud.kubernetes.secrets.enabled=true", "spring.cloud.kubernetes.client.namespace=default"); assertThat(getContext().containsBean("configMapPropertySourceLocator")).isTrue(); assertThat(getContext().containsBean("secretsPropertySourceLocator")).isTrue(); } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/MissingActuatorTest.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/MissingActuatorTest.java index cacd7624..f8a26f8d 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/MissingActuatorTest.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/MissingActuatorTest.java @@ -37,13 +37,14 @@ import static org.assertj.core.api.Assertions.assertThat; @ClassPathExclusions({ "spring-boot-actuator-autoconfigure-*.jar", "spring-boot-starter-actuator-*.jar" }) public class MissingActuatorTest { - private static ConfigurableApplicationContext getApplicationContext(Class configuration, String... properties) { - return new SpringApplicationBuilder(configuration).web(WebApplicationType.NONE).properties(properties).run(); + private static ConfigurableApplicationContext getApplicationContext(String... properties) { + return new SpringApplicationBuilder(Config.class).web(WebApplicationType.NONE).properties(properties).run(); } @Test public void unknownClassProtected(CapturedOutput capturedOutput) { - try (ConfigurableApplicationContext context = getApplicationContext(Config.class, "debug=true")) { + try (ConfigurableApplicationContext context = getApplicationContext("debug=true", + "spring.cloud.kubernetes.client.namespace=default")) { String output = capturedOutput.toString(); assertThat(output) .doesNotContain("Failed to introspect annotations on"