namespace resolution must work the same across callers (#906)

This commit is contained in:
erabii
2021-11-10 15:22:25 -05:00
committed by GitHub
parent 14729f3c72
commit b41dcc146e
15 changed files with 285 additions and 145 deletions

View File

@@ -32,6 +32,8 @@ import org.springframework.cloud.kubernetes.commons.config.ConfigMapPropertySour
import org.springframework.core.env.Environment;
import org.springframework.util.CollectionUtils;
import static org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigUtils.getApplicationNamespace;
/**
* @author Ryan Baxter
* @author Isik Erhan
@@ -43,13 +45,15 @@ public class KubernetesClientConfigMapPropertySource extends ConfigMapPropertySo
@Deprecated
public KubernetesClientConfigMapPropertySource(CoreV1Api coreV1Api, String name, String namespace,
Environment environment) {
super(getName(name, namespace), getData(coreV1Api, name, namespace, environment, "", true, false));
super(getName(name, getApplicationNamespace(namespace, "Config Map", null)), getData(coreV1Api, name,
getApplicationNamespace(namespace, "Config Map", null), environment, "", true, false));
}
public KubernetesClientConfigMapPropertySource(CoreV1Api coreV1Api, String name, String namespace,
Environment environment, String prefix, boolean includeProfileSpecificSources, boolean failFast) {
super(getName(name, namespace),
getData(coreV1Api, name, namespace, environment, prefix, includeProfileSpecificSources, failFast));
super(getName(name, getApplicationNamespace(namespace, "Config Map", null)),
getData(coreV1Api, name, getApplicationNamespace(namespace, "Config Map", null), environment, prefix,
includeProfileSpecificSources, failFast));
}
private static Map<String, Object> getData(CoreV1Api coreV1Api, String name, String namespace,

View File

@@ -95,7 +95,7 @@ public final class KubernetesClientConfigUtils {
static String getApplicationNamespace(String namespace, String configurationTarget,
KubernetesNamespaceProvider provider) {
if (StringUtils.hasText(namespace)) {
LOG.debug(configurationTarget + " namespace from normalized source : " + namespace);
LOG.debug(configurationTarget + " namespace from normalized source or passed directly : " + namespace);
return namespace;
}

View File

@@ -20,6 +20,7 @@ import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import io.kubernetes.client.openapi.models.V1Secret;
@@ -27,9 +28,10 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.kubernetes.commons.config.SecretsPropertySource;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
import static org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigUtils.getApplicationNamespace;
/**
* @author Ryan Baxter
* @author Isik Erhan
@@ -38,49 +40,32 @@ public class KubernetesClientSecretsPropertySource extends SecretsPropertySource
private static final Log LOG = LogFactory.getLog(KubernetesClientSecretsPropertySource.class);
private CoreV1Api coreV1Api;
public KubernetesClientSecretsPropertySource(CoreV1Api coreV1Api, String name, String namespace,
Environment environment, Map<String, String> labels, boolean failFast) {
super(getSourceName(name, namespace), getSourceData(coreV1Api, environment, name, namespace, labels, failFast));
Map<String, String> labels, boolean failFast) {
super(getSourceName(name, getApplicationNamespace(namespace, "Secret", null)),
getSourceData(coreV1Api, name, getApplicationNamespace(namespace, "Secret", null), labels, failFast));
}
private static Map<String, Object> getSourceData(CoreV1Api api, Environment env, String name, String namespace,
private static Map<String, Object> getSourceData(CoreV1Api api, String name, String namespace,
Map<String, String> labels, boolean failFast) {
Map<String, Object> result = new HashMap<>();
LOG.info("Loading Secret with name '" + name + "' or with labels [" + labels + "] in namespace '" + namespace
+ "'");
try {
// Read for secrets api (named)
if (StringUtils.hasText(name)) {
Optional<V1Secret> secret;
if (!StringUtils.hasText(namespace)) {
// There could technically be more than one, just return the first
secret = api.listSecretForAllNamespaces(null, null, null, null, null, null, null, null, null, null)
.getItems().stream().filter(s -> name.equals(s.getMetadata().getName())).findFirst();
}
else {
secret = api
.listNamespacedSecret(namespace, null, null, null, null, null, null, null, null, null, null)
.getItems().stream().filter(s -> name.equals(s.getMetadata().getName())).findFirst();
}
secret = api.listNamespacedSecret(namespace, null, null, null, null, null, null, null, null, null, null)
.getItems().stream().filter(s -> name.equals(s.getMetadata().getName())).findFirst();
secret.ifPresent(s -> putAll(s, result));
}
// Read for secrets api (label)
if (labels != null && !labels.isEmpty()) {
if (!StringUtils.hasText(namespace)) {
api.listSecretForAllNamespaces(null, null, null, createLabelsSelector(labels), null, null, null,
null, null, null).getItems().forEach(s -> putAll(s, result));
}
else {
api.listNamespacedSecret(namespace, null, null, null, null, createLabelsSelector(labels), null,
null, null, null, null).getItems().forEach(s -> putAll(s, result));
}
api.listNamespacedSecret(namespace, null, null, null, null, createLabelsSelector(labels), null, null,
null, null, null).getItems().forEach(s -> putAll(s, result));
}
}
catch (Exception e) {
@@ -97,20 +82,13 @@ public class KubernetesClientSecretsPropertySource extends SecretsPropertySource
}
private static String createLabelsSelector(Map<String, String> labels) {
StringBuilder selectorString = new StringBuilder();
for (String key : labels.keySet()) {
if (selectorString.length() != 0) {
selectorString.append(",");
}
selectorString.append(key + "=" + labels.get(key));
}
return selectorString.toString();
return labels.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining(","));
}
private static void putAll(V1Secret secret, Map<String, Object> result) {
Map<String, String> secretData = new HashMap<>();
secret.getData().forEach((key, value) -> secretData.put(key, Base64.getEncoder().encodeToString(value)));
if (secret != null) {
if (secret.getData() != null) {
secret.getData().forEach((key, value) -> secretData.put(key, Base64.getEncoder().encodeToString(value)));
putAll(secretData, result);
}
}

View File

@@ -90,8 +90,8 @@ public class KubernetesClientSecretsPropertySourceLocator extends SecretsPropert
kubernetesNamespaceProvider);
}
return new KubernetesClientSecretsPropertySource(coreV1Api, secretName, namespace, environment,
normalizedSource.getLabels(), this.properties.isFailFast());
return new KubernetesClientSecretsPropertySource(coreV1Api, secretName, namespace, normalizedSource.getLabels(),
this.properties.isFailFast());
}
}

View File

@@ -31,6 +31,7 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.cloud.kubernetes.commons.config.NamespaceResolutionFailedException;
import org.springframework.mock.env.MockEnvironment;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
@@ -144,6 +145,30 @@ class KubernetesClientConfigMapPropertySourceTests {
.isEqualTo("TRACE");
}
@Test
void deprecatedConstructorWithoutNamespaceMustFail() {
assertThatThrownBy(() -> new KubernetesClientConfigMapPropertySource(new CoreV1Api(), "configmap", null,
new MockEnvironment())).isInstanceOf(NamespaceResolutionFailedException.class);
}
@Test
void constructorWithoutNamespaceMustFail() {
assertThatThrownBy(() -> new KubernetesClientConfigMapPropertySource(new CoreV1Api(), "configmap", null,
new MockEnvironment(), "", false, false)).isInstanceOf(NamespaceResolutionFailedException.class);
}
@Test
void deprecatedConstructorWithNamespaceMustNotFail() {
assertThat(new KubernetesClientConfigMapPropertySource(new CoreV1Api(), "configmap", "namespace",
new MockEnvironment())).isNotNull();
}
@Test
void constructorWithNamespaceMustNotFail() {
assertThat(new KubernetesClientConfigMapPropertySource(new CoreV1Api(), "configmap", "namespace",
new MockEnvironment(), "", false, false)).isNotNull();
}
@Test
public void constructorShouldThrowExceptionOnFailureWhenFailFastIsEnabled() {
CoreV1Api api = new CoreV1Api();

View File

@@ -35,7 +35,7 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.cloud.kubernetes.commons.config.NamespaceResolutionFailedException;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
@@ -63,7 +63,7 @@ class KubernetesClientSecretsPropertySourceTests {
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_WITH_LABEL = "/api/v1/namespaces/default/secrets?labelSelector=spring.cloud.kubernetes.secret%3Dtrue";
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"
@@ -91,7 +91,7 @@ class KubernetesClientSecretsPropertySourceTests {
private static WireMockServer wireMockServer;
@BeforeAll
public static void setup() {
static void setup() {
wireMockServer = new WireMockServer(options().dynamicPort());
wireMockServer.start();
@@ -103,21 +103,21 @@ class KubernetesClientSecretsPropertySourceTests {
}
@AfterAll
public static void after() {
static void after() {
wireMockServer.stop();
}
@AfterEach
public void afterEach() {
void afterEach() {
WireMock.reset();
}
@Test
public void secretsTest() {
void secretsTest() {
CoreV1Api api = new CoreV1Api();
stubFor(get(API).willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(SECRET_LIST))));
KubernetesClientSecretsPropertySource propertySource = new KubernetesClientSecretsPropertySource(api,
"db-secret", "default", new MockEnvironment(), new HashMap<>(), false);
"db-secret", "default", new HashMap<>(), false);
assertThat(propertySource.containsProperty("password")).isTrue();
assertThat(propertySource.getProperty("password")).isEqualTo("p455w0rd");
assertThat(propertySource.containsProperty("username")).isTrue();
@@ -125,48 +125,43 @@ class KubernetesClientSecretsPropertySourceTests {
}
@Test
public void secretsNullNamespaceTest() {
CoreV1Api api = new CoreV1Api();
stubFor(get(LIST_API).willReturn(aResponse().withStatus(200).withBody(LIST_BODY)));
KubernetesClientSecretsPropertySource propertySource = new KubernetesClientSecretsPropertySource(api,
"db-secret", null, new MockEnvironment(), new HashMap<>(), false);
assertThat(propertySource.containsProperty("password")).isTrue();
assertThat(propertySource.getProperty("password")).isEqualTo("p455w0rd");
assertThat(propertySource.containsProperty("username")).isTrue();
assertThat(propertySource.getProperty("username")).isEqualTo("user");
}
@Test
public void secretLabelsTest() {
void secretLabelsTest() {
CoreV1Api api = new CoreV1Api();
stubFor(get(LIST_API_WITH_LABEL).willReturn(aResponse().withStatus(200).withBody(LIST_BODY)));
Map<String, String> labels = new HashMap<>();
labels.put("spring.cloud.kubernetes.secret", "true");
KubernetesClientSecretsPropertySource propertySource = new KubernetesClientSecretsPropertySource(api, null,
null, new MockEnvironment(), labels, false);
"default", labels, false);
assertThat(propertySource.containsProperty("spring.rabbitmq.password")).isTrue();
assertThat(propertySource.getProperty("spring.rabbitmq.password")).isEqualTo("password");
}
@Test
public void constructorShouldThrowExceptionOnFailureWhenFailFastIsEnabled() {
void constructorShouldThrowExceptionOnFailureWhenFailFastIsEnabled() {
CoreV1Api api = new CoreV1Api();
stubFor(get(API).willReturn(aResponse().withStatus(500).withBody("Internal Server Error")));
assertThatThrownBy(() -> new KubernetesClientSecretsPropertySource(api, "db-secret", "default",
new MockEnvironment(), null, true)).isInstanceOf(IllegalStateException.class).hasMessage(
"Unable to read Secret with name 'db-secret' or labels [null] in namespace 'default'");
assertThatThrownBy(() -> new KubernetesClientSecretsPropertySource(api, "db-secret", "default", null, true))
.isInstanceOf(IllegalStateException.class)
.hasMessage("Unable to read Secret with name 'db-secret' or labels [null] in namespace 'default'");
verify(getRequestedFor(urlEqualTo(API)));
}
@Test
public void constructorShouldNotThrowExceptionOnFailureWhenFailFastIsDisabled() {
void constructorShouldNotThrowExceptionOnFailureWhenFailFastIsDisabled() {
CoreV1Api api = new CoreV1Api();
stubFor(get(API).willReturn(aResponse().withStatus(500).withBody("Internal Server Error")));
assertThatNoException().isThrownBy((() -> new KubernetesClientSecretsPropertySource(api, "db-secret", "default",
new MockEnvironment(), null, false)));
assertThatNoException().isThrownBy(
(() -> new KubernetesClientSecretsPropertySource(api, "db-secret", "default", null, false)));
verify(getRequestedFor(urlEqualTo(API)));
}
@Test
void constructorMustFailWhenNamespaceIsNoProvided() {
CoreV1Api api = new CoreV1Api();
assertThatThrownBy((() -> new KubernetesClientSecretsPropertySource(api, "db-secret", null, null, false)))
.isInstanceOf(NamespaceResolutionFailedException.class);
}
}

View File

@@ -83,7 +83,7 @@ public class KubernetesConfigServerAutoConfiguration {
List<String> namespaces = namespaceSplitter(properties.getSecretsNamespaces(), namespace);
List<MapPropertySource> propertySources = new ArrayList<>();
namespaces.forEach(space -> propertySources.add(new KubernetesClientSecretsPropertySource(coreApi,
applicationName, space, springEnv, new HashMap<>(), false)));
applicationName, space, new HashMap<>(), false)));
return propertySources;
};
}

View File

@@ -105,7 +105,7 @@ class KubernetesEnvironmentRepositoryTests {
kubernetesPropertySourceSuppliers.add((coreApi, applicationName, namespace, springEnv) -> {
List<MapPropertySource> propertySources = new ArrayList<>();
propertySources.add(new KubernetesClientSecretsPropertySource(coreApi, applicationName, "default",
springEnv, new HashMap<>(), false));
new HashMap<>(), false));
return propertySources;
});
}

View File

@@ -44,6 +44,11 @@ public class Fabric8ConfigMapPropertySource extends ConfigMapPropertySource {
private static final Log LOG = LogFactory.getLog(Fabric8ConfigMapPropertySource.class);
/**
* this constructor is present only for compatibility reasons, its usage is
* discouraged.
*/
@Deprecated
public Fabric8ConfigMapPropertySource(KubernetesClient client, String name) {
this(client, name, null, null, "", true, false);
}
@@ -55,15 +60,15 @@ public class Fabric8ConfigMapPropertySource extends ConfigMapPropertySource {
@Deprecated
public Fabric8ConfigMapPropertySource(KubernetesClient client, String name, String namespace,
Environment environment) {
super(getName(name, getApplicationNamespace(client, namespace)),
getData(client, name, getApplicationNamespace(client, namespace), environment, "", true, false));
super(getName(name, getApplicationNamespace(client, namespace, "Config Map", null)), getData(client, name,
getApplicationNamespace(client, namespace, "Config Map", null), environment, "", true, false));
}
public Fabric8ConfigMapPropertySource(KubernetesClient client, String name, String namespace,
Environment environment, String prefix, boolean includeProfileSpecificSources, boolean failFast) {
super(getName(name, getApplicationNamespace(client, namespace)),
getData(client, name, getApplicationNamespace(client, namespace), environment, prefix,
includeProfileSpecificSources, failFast));
super(getName(name, getApplicationNamespace(client, namespace, "Config Map", null)),
getData(client, name, getApplicationNamespace(client, namespace, "Config Map", null), environment,
prefix, includeProfileSpecificSources, failFast));
}
private static Map<String, Object> getData(KubernetesClient client, String name, String namespace,

View File

@@ -55,6 +55,14 @@ public final class Fabric8ConfigUtils {
return namespace;
}
/*
* this is not used, it is here for compatibility reasons only.
*/
@Deprecated
public static String getApplicationNamespace(KubernetesClient client, String namespace) {
return !StringUtils.hasLength(namespace) ? client.getNamespace() : namespace;
}
/**
* this method does the namespace resolution for both config map and secrets
* implementations. It tries these places to find the namespace:
@@ -81,7 +89,7 @@ public final class Fabric8ConfigUtils {
KubernetesNamespaceProvider provider) {
if (StringUtils.hasText(namespace)) {
LOG.debug(configurationTarget + " namespace from normalized source : " + namespace);
LOG.debug(configurationTarget + " namespace from normalized source or passed directly : " + namespace);
return namespace;
}
@@ -102,13 +110,12 @@ public final class Fabric8ConfigUtils {
}
public static String getApplicationNamespace(KubernetesClient client, String namespace) {
return !StringUtils.hasLength(namespace) ? client.getNamespace() : namespace;
}
/*
* namespace that reaches this point is absolutely present, otherwise this would have
* resulted in a NamespaceResolutionFailedException
*/
static Map<String, String> getConfigMapData(KubernetesClient client, String namespace, String name) {
ConfigMap configMap = !StringUtils.hasLength(namespace) ? client.configMaps().withName(name).get()
: client.configMaps().inNamespace(namespace).withName(name).get();
ConfigMap configMap = client.configMaps().inNamespace(namespace).withName(name).get();
if (configMap == null) {
LOG.warn("config-map with name : '" + name + "' not present in namespace : '" + namespace + "'");

View File

@@ -25,7 +25,8 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.kubernetes.commons.config.SecretsPropertySource;
import org.springframework.util.StringUtils;
import static org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigUtils.getApplicationNamespace;
/**
* Kubernetes property source for secrets.
@@ -40,40 +41,40 @@ public class Fabric8SecretsPropertySource extends SecretsPropertySource {
public Fabric8SecretsPropertySource(KubernetesClient client, String name, String namespace,
Map<String, String> labels, boolean failFast) {
super(getSourceName(name, namespace), getSourceData(client, name, namespace, labels, failFast));
super(getSourceName(name, getApplicationNamespace(client, namespace, "Secret", null)), getSourceData(client,
name, getApplicationNamespace(client, namespace, "Secret", null), labels, failFast));
}
private static Map<String, Object> getSourceData(KubernetesClient client, String name, String namespace,
Map<String, String> labels, boolean failFast) {
Map<String, Object> result = new HashMap<>();
String namespaceToUse = StringUtils.hasLength(namespace) ? namespace : client.getNamespace();
LOG.info("Loading Secret with name '" + name + "' or with labels [" + labels + "] in namespace '"
+ namespaceToUse + "'");
LOG.info("Loading Secret with name '" + name + "' or with labels [" + labels + "] in namespace '" + namespace
+ "'");
try {
Secret secret = client.secrets().inNamespace(namespaceToUse).withName(name).get();
Secret secret = client.secrets().inNamespace(namespace).withName(name).get();
// the API is documented that it might return null
if (secret == null) {
LOG.warn("secret with name : " + name + " in namespace : " + namespaceToUse + " not found");
LOG.warn("secret with name : " + name + " in namespace : " + namespace + " not found");
}
else {
putDataFromSecret(secret, result, namespaceToUse);
putDataFromSecret(secret, result, namespace);
}
client.secrets().inNamespace(namespaceToUse).withLabels(labels).list().getItems()
.forEach(s -> putDataFromSecret(s, result, namespaceToUse));
client.secrets().inNamespace(namespace).withLabels(labels).list().getItems()
.forEach(s -> putDataFromSecret(s, result, namespace));
}
catch (Exception e) {
if (failFast) {
throw new IllegalStateException("Unable to read Secret with name '" + name + "' or labels [" + labels
+ "] in namespace '" + namespaceToUse + "'", e);
+ "] in namespace '" + namespace + "'", e);
}
LOG.warn("Can't read secret with name: [" + name + "] or labels [" + labels + "] in namespace: ["
+ namespaceToUse + "] (cause: " + e.getMessage() + "). Ignoring");
+ namespace + "] (cause: " + e.getMessage() + "). Ignoring");
}
return result;

View File

@@ -16,13 +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 io.fabric8.kubernetes.client.server.mock.KubernetesMockServer;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.cloud.kubernetes.commons.config.NamespaceResolutionFailedException;
import org.springframework.mock.env.MockEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -30,14 +34,16 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
* @author Isik Erhan
*/
@EnableKubernetesMockClient
public class Fabric8ConfigMapPropertySourceTests {
class Fabric8ConfigMapPropertySourceTests {
KubernetesMockServer mockServer;
private KubernetesMockServer mockServer;
KubernetesClient mockClient;
private KubernetesClient mockClient;
private final DefaultKubernetesClient client = Mockito.mock(DefaultKubernetesClient.class);
@Test
public void constructorShouldThrowExceptionOnFailureWhenFailFastIsEnabled() {
void constructorShouldThrowExceptionOnFailureWhenFailFastIsEnabled() {
final String name = "my-config";
final String namespace = "default";
final String path = String.format("/api/v1/namespaces/%s/configmaps/%s", namespace, name);
@@ -49,7 +55,7 @@ public class Fabric8ConfigMapPropertySourceTests {
}
@Test
public void constructorShouldNotThrowExceptionOnFailureWhenFailFastIsDisabled() {
void constructorShouldNotThrowExceptionOnFailureWhenFailFastIsDisabled() {
final String name = "my-config";
final String namespace = "default";
final String path = String.format("/api/v1/namespaces/%s/configmaps/%s", namespace, name);
@@ -59,4 +65,65 @@ public class Fabric8ConfigMapPropertySourceTests {
new MockEnvironment(), "", false, false));
}
@Test
void deprecatedConstructorWithoutClientNamespaceMustFail() {
Mockito.when(client.getNamespace()).thenReturn(null);
assertThatThrownBy(() -> new Fabric8ConfigMapPropertySource(client, "configmap"))
.isInstanceOf(NamespaceResolutionFailedException.class);
}
@Test
void deprecatedConstructorWithClientNamespaceMustNotFail() {
Mockito.when(client.getNamespace()).thenReturn("some");
assertThat(new Fabric8ConfigMapPropertySource(client, "configmap")).isNotNull();
}
@Test
void anotherDeprecatedConstructorWithoutClientNamespaceMustFail() {
Mockito.when(client.getNamespace()).thenReturn(null);
assertThatThrownBy(() -> new Fabric8ConfigMapPropertySource(client, "configmap", null, new MockEnvironment()))
.isInstanceOf(NamespaceResolutionFailedException.class);
}
@Test
void anotherDeprecatedConstructorWithClientNamespaceMustNotFail() {
Mockito.when(client.getNamespace()).thenReturn("some-namespace");
assertThat(new Fabric8ConfigMapPropertySource(client, "configmap", null, new MockEnvironment())).isNotNull();
}
@Test
void anotherDeprecatedConstructorWithNamespaceMustNotFail() {
Mockito.when(client.getNamespace()).thenReturn(null);
assertThat(new Fabric8ConfigMapPropertySource(client, "configmap", "namespace", new MockEnvironment()))
.isNotNull();
}
@Test
void constructorWithoutClientNamespaceMustFail() {
Mockito.when(client.getNamespace()).thenReturn(null);
assertThatThrownBy(() -> new Fabric8ConfigMapPropertySource(client, "configmap", null, new MockEnvironment()))
.isInstanceOf(NamespaceResolutionFailedException.class);
}
@Test
void constructorWithClientNamespaceMustNotFail() {
Mockito.when(client.getNamespace()).thenReturn("namespace");
assertThat(new Fabric8ConfigMapPropertySource(client, "configmap", null, new MockEnvironment())).isNotNull();
}
@Test
void constructorWithNamespaceMustNotFail() {
Mockito.when(client.getNamespace()).thenReturn(null);
assertThat(new Fabric8ConfigMapPropertySource(client, "configmap", "namespace", new MockEnvironment()))
.isNotNull();
}
}

View File

@@ -0,0 +1,94 @@
/*
* 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;
import java.util.Collections;
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
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.NamespaceResolutionFailedException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* tests that are supposed to use EnableKubernetesMockClient only
*
* @author wind57
*/
@EnableKubernetesMockClient
class Fabric8SecretsPropertySourceMockTests {
private static KubernetesMockServer mockServer;
private static KubernetesClient client;
private final DefaultKubernetesClient mockClient = Mockito.mock(DefaultKubernetesClient.class);
@Test
void constructorShouldThrowExceptionOnFailureWhenFailFastIsEnabled() {
final String name = "my-config";
final String namespace = "default";
final String path = String.format("/api/v1/namespaces/%s/secrets/%s", namespace, name);
mockServer.expect().withPath(path).andReturn(500, "Internal Server Error").once();
assertThatThrownBy(
() -> new Fabric8SecretsPropertySource(client, name, namespace, Collections.emptyMap(), true))
.isInstanceOf(IllegalStateException.class).hasMessage("Unable to read Secret with name '" + name
+ "' or labels [{}] in namespace '" + namespace + "'");
}
@Test
void constructorShouldNotThrowExceptionOnFailureWhenFailFastIsDisabled() {
final String name = "my-config";
final String namespace = "default";
final String path = String.format("/api/v1/namespaces/%s/secrets/%s", namespace, name);
mockServer.expect().withPath(path).andReturn(500, "Internal Server Error").once();
assertThatNoException().isThrownBy(
() -> new Fabric8SecretsPropertySource(client, name, namespace, Collections.emptyMap(), false));
}
@Test
void constructorWithoutClientNamespaceMustFail() {
Mockito.when(mockClient.getNamespace()).thenReturn(null);
assertThatThrownBy(
() -> new Fabric8SecretsPropertySource(mockClient, "my-secret", null, Collections.emptyMap(), false))
.isInstanceOf(NamespaceResolutionFailedException.class);
}
@Test
void constructorWithClientNamespaceMustNotFail() {
Mockito.when(mockClient.getNamespace()).thenReturn("namespace");
assertThat(new Fabric8SecretsPropertySource(mockClient, "my-secret", null, Collections.emptyMap(), false))
.isNotNull();
}
@Test
void constructorWithNamespaceMustNotFail() {
Mockito.when(mockClient.getNamespace()).thenReturn(null);
assertThat(new Fabric8SecretsPropertySource(mockClient, "my-secret", "ns", Collections.emptyMap(), false))
.isNotNull();
}
}

View File

@@ -17,16 +17,12 @@
package org.springframework.cloud.kubernetes.fabric8.config;
import java.util.Base64;
import java.util.Collections;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.api.model.SecretBuilder;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
import io.fabric8.kubernetes.client.server.mock.KubernetesServer;
import org.junit.ClassRule;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -41,23 +37,18 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
import static java.util.Collections.singletonMap;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class,
properties = "spring.main.cloud-platform=KUBERNETES")
@TestPropertySource("classpath:/application-secrets.properties")
@EnableKubernetesMockClient(crud = true, https = false)
public class Fabric8SecretsPropertySourceTest {
class Fabric8SecretsPropertySourceTest {
private static final String NAMESPACE = "test";
private static KubernetesClient mockClient;
@ClassRule
public static KubernetesServer mockServer = new KubernetesServer(false);
private static final String SECRET_VALUE = "secretValue";
@Autowired
@@ -67,7 +58,7 @@ public class Fabric8SecretsPropertySourceTest {
private Environment environment;
@BeforeAll
public static void setUpBeforeClass() {
static void setUpBeforeClass() {
// Configure the kubernetes master url to point to the mock server
System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl());
@@ -82,43 +73,13 @@ public class Fabric8SecretsPropertySourceTest {
.addToData("secretName", Base64.getEncoder().encodeToString(SECRET_VALUE.getBytes())).build();
mockClient.secrets().inNamespace(NAMESPACE).create(secret);
mockServer.before();
}
@AfterAll
public static void tearDown() {
mockServer.after();
}
@Test
public void toStringShouldNotExposeSecretValues() {
void toStringShouldNotExposeSecretValues() {
PropertySource<?> propertySource = this.propertySourceLocator.locate(this.environment);
assertThat(propertySource.toString()).doesNotContain(SECRET_VALUE);
assertThat(propertySource.getProperty("secretName")).isEqualTo("secretValue");
}
@Test
public void constructorShouldThrowExceptionOnFailureWhenFailFastIsEnabled() {
final String name = "my-config";
final String namespace = "default";
final String path = String.format("/api/v1/namespaces/%s/secrets/%s", namespace, name);
mockServer.expect().withPath(path).andReturn(500, "Internal Server Error").once();
assertThatThrownBy(() -> new Fabric8SecretsPropertySource(mockServer.getClient(), name, namespace,
Collections.emptyMap(), true)).isInstanceOf(IllegalStateException.class)
.hasMessage("Unable to read Secret with name '" + name + "' or labels [{}] in namespace '"
+ namespace + "'");
}
@Test
public void constructorShouldNotThrowExceptionOnFailureWhenFailFastIsDisabled() {
final String name = "my-config";
final String namespace = "default";
final String path = String.format("/api/v1/namespaces/%s/secrets/%s", namespace, name);
mockServer.expect().withPath(path).andReturn(500, "Internal Server Error").once();
assertThatNoException().isThrownBy(() -> new Fabric8SecretsPropertySource(mockServer.getClient(), name,
namespace, Collections.emptyMap(), false));
}
}

View File

@@ -55,14 +55,17 @@ public class EventBasedConfigurationChangeDetectorTests {
data.put("foo", "bar");
configMap.setData(data);
MixedOperation<ConfigMap, ConfigMapList, Resource<ConfigMap>> mixedOperation = mock(MixedOperation.class);
when(k8sClient.configMaps()).thenReturn(mixedOperation);
Resource<ConfigMap> resource = mock(Resource.class);
when(resource.get()).thenReturn(configMap);
when(mixedOperation.withName(eq("myconfigmap"))).thenReturn(resource);
when(k8sClient.configMaps()).thenReturn(mixedOperation);
when(mixedOperation.inNamespace("default")).thenReturn(mixedOperation);
when(k8sClient.getNamespace()).thenReturn("default");
Fabric8ConfigMapPropertySource fabric8ConfigMapPropertySource = new Fabric8ConfigMapPropertySource(k8sClient,
"myconfigmap");
env.getPropertySources().addFirst(new BootstrapPropertySource(fabric8ConfigMapPropertySource));
env.getPropertySources().addFirst(new BootstrapPropertySource<>(fabric8ConfigMapPropertySource));
ConfigurationUpdateStrategy configurationUpdateStrategy = mock(ConfigurationUpdateStrategy.class);
Fabric8ConfigMapPropertySourceLocator configMapLocator = mock(Fabric8ConfigMapPropertySourceLocator.class);