AbstractKubernetesProfileEnvironmentPostProcessor changes + tests (#738)

* initial commit

* slightly make test more readable

* added test scope
This commit is contained in:
erabii
2021-02-23 12:31:14 -05:00
committed by GitHub
parent 5b0e104f5d
commit 77fac841b2
4 changed files with 300 additions and 45 deletions

View File

@@ -50,6 +50,12 @@
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<exclusions>
<exclusion>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
</exclusion>
</exclusions>
<scope>test</scope>
</dependency>
<dependency>
@@ -57,6 +63,17 @@
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

View File

@@ -67,7 +67,7 @@ public abstract class AbstractKubernetesHealthIndicator extends AbstractHealthIn
public static final String LABELS = "labels";
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
protected void doHealthCheck(Health.Builder builder) {
try {
builder.up().withDetails(getDetails());
}

View File

@@ -16,19 +16,17 @@
package org.springframework.cloud.kubernetes.commons.profile;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.logging.DeferredLog;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
@@ -41,7 +39,7 @@ import static org.springframework.cloud.kubernetes.commons.KubernetesClientPrope
*/
public abstract class AbstractKubernetesProfileEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
private static final Log LOG = LogFactory.getLog(AbstractKubernetesProfileEnvironmentPostProcessor.class);
private static final DeferredLog LOG = new DeferredLog();
private static final String NAMESPACE_PATH_PROPERTY = "spring.cloud.kubernetes.client.serviceAccountNamespacePath";
@@ -60,41 +58,20 @@ public abstract class AbstractKubernetesProfileEnvironmentPostProcessor implemen
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
final boolean kubernetesEnabled = environment.getProperty("spring.cloud.kubernetes.enabled", Boolean.class,
true);
application.addInitializers(ctx -> LOG.replayTo(AbstractKubernetesProfileEnvironmentPostProcessor.class));
boolean kubernetesEnabled = environment.getProperty("spring.cloud.kubernetes.enabled", Boolean.class, true);
if (!kubernetesEnabled) {
return;
}
addNamespaceFromServiceAccountFile(environment);
if (isInsideKubernetes(environment)) {
if (hasKubernetesProfile(environment)) {
if (LOG.isDebugEnabled()) {
LOG.debug("'kubernetes' already in list of active profiles");
}
}
else {
if (LOG.isDebugEnabled()) {
LOG.debug("Adding 'kubernetes' to list of active profiles");
}
environment.addActiveProfile(KUBERNETES_PROFILE);
}
}
else {
if (LOG.isDebugEnabled()) {
LOG.warn("Not running inside kubernetes. Skipping 'kubernetes' profile activation.");
}
}
addKubernetesProfileIfMissing(environment);
}
protected abstract boolean isInsideKubernetes(Environment environment);
private boolean hasKubernetesProfile(Environment environment) {
for (String activeProfile : environment.getActiveProfiles()) {
if (KUBERNETES_PROFILE.equalsIgnoreCase(activeProfile)) {
return true;
}
}
return false;
return Arrays.stream(environment.getActiveProfiles()).anyMatch(KUBERNETES_PROFILE::equalsIgnoreCase);
}
@Override
@@ -102,30 +79,43 @@ public abstract class AbstractKubernetesProfileEnvironmentPostProcessor implemen
return ORDER;
}
private void addNamespaceFromServiceAccountFile(ConfigurableEnvironment environment) {
Map<String, Object> properties = new HashMap<>();
private void addKubernetesProfileIfMissing(ConfigurableEnvironment environment) {
if (isInsideKubernetes(environment)) {
if (hasKubernetesProfile(environment)) {
LOG.debug("'kubernetes' already in list of active profiles");
}
else {
LOG.debug("Adding 'kubernetes' to list of active profiles");
environment.addActiveProfile(KUBERNETES_PROFILE);
}
}
else {
LOG.warn("Not running inside kubernetes. Skipping 'kubernetes' profile activation.");
}
}
private void addNamespaceFromServiceAccountFile(ConfigurableEnvironment environment) {
String serviceAccountNamespace = environment.getProperty(NAMESPACE_PATH_PROPERTY,
SERVICE_ACCOUNT_NAMESPACE_PATH);
LOG.debug("Looking for service account namespace at " + serviceAccountNamespace);
boolean serviceAccountNamespaceExists = Files.isRegularFile((new File(serviceAccountNamespace)).toPath(),
new LinkOption[0]);
LOG.debug("Looking for service account namespace at: [" + serviceAccountNamespace + "].");
Path serviceAccountNamespacePath = Paths.get(serviceAccountNamespace);
boolean serviceAccountNamespaceExists = Files.isRegularFile(serviceAccountNamespacePath);
if (serviceAccountNamespaceExists) {
LOG.debug("Found service account namespace at: [" + serviceAccountNamespace + "].");
try {
String namespace = new String(Files.readAllBytes((new File(serviceAccountNamespace)).toPath()));
String namespace = new String(Files.readAllBytes((serviceAccountNamespacePath)));
LOG.debug("Service account namespace value: " + namespace);
properties.put(NAMESPACE_PROPERTY, namespace);
environment.getPropertySources().addLast(new MapPropertySource(PROPERTY_SOURCE_NAME,
Collections.singletonMap(NAMESPACE_PROPERTY, namespace)));
}
catch (IOException var4) {
LOG.error("Error reading service account namespace from: [" + serviceAccountNamespace + "].", var4);
catch (IOException ioe) {
LOG.error("Error reading service account namespace from: [" + serviceAccountNamespace + "].", ioe);
}
}
else {
LOG.info("Did not find service account namespace at: [" + serviceAccountNamespace + "]. Ignoring.");
}
environment.getPropertySources().addLast(new MapPropertySource(PROPERTY_SOURCE_NAME, properties));
}
}

View File

@@ -0,0 +1,248 @@
/*
* 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.profile;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Optional;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.test.context.support.TestPropertySourceUtils;
/**
* @author wind57
*/
public class AbstractKubernetesProfileEnvironmentPostProcessorTest {
private static final String FOUNT_IT = "foundIt";
private static final String PATH = "/some/path";
private MockedStatic<Paths> paths;
private MockedStatic<Files> files;
private final SpringApplication springApplication = Mockito.mock(SpringApplication.class);
private final ConfigurableApplicationContext context = new AnnotationConfigApplicationContext();
private static final AbstractKubernetesProfileEnvironmentPostProcessor POST_PROCESSOR_INSIDE = new AbstractKubernetesProfileEnvironmentPostProcessor() {
@Override
protected boolean isInsideKubernetes(Environment environment) {
return true;
}
};
private static final AbstractKubernetesProfileEnvironmentPostProcessor POST_PROCESSOR_OUTSIDE = new AbstractKubernetesProfileEnvironmentPostProcessor() {
@Override
protected boolean isInsideKubernetes(Environment environment) {
return false;
}
};
@Before
public void before() {
paths = Mockito.mockStatic(Paths.class);
files = Mockito.mockStatic(Files.class);
}
@After
public void after() {
paths.close();
files.close();
}
/**
* <pre>
* 1) "spring.cloud.kubernetes.enabled" is false; thus nothing happens
* </pre>
*/
@Test
public void testKubernetesDisabled() {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context, "spring.cloud.kubernetes.enabled=false");
POST_PROCESSOR_INSIDE.postProcessEnvironment(context.getEnvironment(), springApplication);
assertKubernetesProfileNotPresent();
assertKubernetesPropertySourceNotPresent();
}
/**
* <pre>
* 1) "spring.cloud.kubernetes.enabled" is true
* 2) "spring.cloud.kubernetes.client.serviceAccountNamespacePath" is present, but does not resolve to an actual File
* </pre>
*/
@Test
public void testKubernetesEnabledAndServiceAccountNamespacePathIsNotResolved() {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context, "spring.cloud.kubernetes.enabled=true",
"spring.cloud.kubernetes.client.serviceAccountNamespacePath=" + PATH);
serviceAccountFileResolved(false, PATH);
POST_PROCESSOR_INSIDE.postProcessEnvironment(context.getEnvironment(), springApplication);
assertKubernetesProfilePresent();
assertKubernetesPropertySourceNotPresent();
}
/**
* <pre>
* 1) "spring.cloud.kubernetes.enabled" is true
* 2) "spring.cloud.kubernetes.client.serviceAccountNamespacePath" is present and resolves to an actual File
* </pre>
*/
@Test
public void testKubernetesEnabledAndServiceAccountNamespacePathIsResolved() {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context, "spring.cloud.kubernetes.enabled=true",
"spring.cloud.kubernetes.client.serviceAccountNamespacePath=" + PATH);
Path path = serviceAccountFileResolved(true, PATH);
mockServiceAccountNamespace(path);
POST_PROCESSOR_INSIDE.postProcessEnvironment(context.getEnvironment(), springApplication);
assertKubernetesProfilePresent();
assertKubernetesPropertySourcePresent();
}
/**
* <pre>
* 1) "spring.cloud.kubernetes.enabled" is true
* 2) "spring.cloud.kubernetes.client.serviceAccountNamespacePath" is not present, as such:
* 3) "/var/run/secrets/kubernetes.io/serviceaccount/namespace" is picked up, which is resolved and present
* </pre>
*/
@Test
public void testKubernetesEnabledAndServiceAccountNamespacePathIsResolvedViaDefaultLocation() {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context, "spring.cloud.kubernetes.enabled=true");
Path path = serviceAccountFileResolved(true, "/var/run/secrets/kubernetes.io/serviceaccount/namespace");
mockServiceAccountNamespace(path);
POST_PROCESSOR_INSIDE.postProcessEnvironment(context.getEnvironment(), springApplication);
assertKubernetesProfilePresent();
assertKubernetesPropertySourcePresent();
}
/**
* <pre>
* 1) "spring.cloud.kubernetes.enabled" is true
* 2) isInsideKubernetes returns false
* 3) "spring.cloud.kubernetes.client.serviceAccountNamespacePath" is not present, as such:
* 4) "/var/run/secrets/kubernetes.io/serviceaccount/namespace" is picked up, which is resolved and present
* </pre>
*/
@Test
public void testOutsideKubernetes() {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context, "spring.cloud.kubernetes.enabled=true");
Path path = serviceAccountFileResolved(true, "/var/run/secrets/kubernetes.io/serviceaccount/namespace");
mockServiceAccountNamespace(path);
POST_PROCESSOR_OUTSIDE.postProcessEnvironment(context.getEnvironment(), springApplication);
assertKubernetesProfileNotPresent();
assertKubernetesPropertySourcePresent();
}
/*
* 'kubernetes' profile is not present
*/
private void assertKubernetesProfileNotPresent() {
Assert.assertFalse("'kubernetes' profile must not be present when 'spring.cloud.kubernetes.enabled' is false",
kubernetesProfile().isPresent());
}
/*
* 'kubernetes' profile is present
*/
private void assertKubernetesProfilePresent() {
Assert.assertTrue("'kubernetes' profile must be present when 'spring.cloud.kubernetes.enabled' is true",
kubernetesProfile().isPresent());
}
/*
* 'KUBERNETES_NAMESPACE_PROPERTY_SOURCE' source is not present
*/
private void assertKubernetesPropertySourceNotPresent() {
Optional<PropertySource<?>> kubernetesPropertySource = kubernetesPropertySource();
Assert.assertFalse(
"'KUBERNETES_NAMESPACE_PROPERTY_SOURCE' source must not be present when 'spring.cloud.kubernetes.enabled' is false",
kubernetesPropertySource.isPresent());
}
/*
* 'KUBERNETES_NAMESPACE_PROPERTY_SOURCE' source is present
*/
private void assertKubernetesPropertySourcePresent() {
Optional<PropertySource<?>> kubernetesPropertySource = kubernetesPropertySource();
Assert.assertTrue(
"'KUBERNETES_NAMESPACE_PROPERTY_SOURCE' source must be present when 'spring.cloud.kubernetes.enabled' is true",
kubernetesPropertySource.isPresent());
String property = (String) kubernetesPropertySource.get()
.getProperty("spring.cloud.kubernetes.client.namespace");
Assert.assertEquals("'spring.cloud.kubernetes.client.namespace' must be set to 'foundIt'", property, FOUNT_IT);
}
/**
* <pre>
* 1) serviceAccountNamespace File is present or not
* 2) if the above is present, under what actualPath
* </pre>
*/
private Path serviceAccountFileResolved(boolean present, String actualPath) {
Path path = Mockito.mock(Path.class);
paths.when(() -> Paths.get(actualPath)).thenReturn(path);
files.when(() -> Files.isRegularFile(path)).thenReturn(present);
return path;
}
/*
* returns "foundIt" for service account namespace
*/
private void mockServiceAccountNamespace(Path path) {
files.when(() -> Files.readAllBytes(path)).thenReturn(FOUNT_IT.getBytes());
}
private Optional<String> kubernetesProfile() {
return Arrays.stream(context.getEnvironment().getActiveProfiles()).filter("kubernetes"::equals).findFirst();
}
private Optional<PropertySource<?>> kubernetesPropertySource() {
return context.getEnvironment().getPropertySources().stream()
.filter(x -> "KUBERNETES_NAMESPACE_PROPERTY_SOURCE".equals(x.getName())).findAny();
}
}