Improve the structure of the response from the env endpoint

Closes gh-9864
This commit is contained in:
Andy Wilkinson 2017-08-01 07:38:24 +01:00
parent 28b7ec3bed
commit 8f42007aa1
2 changed files with 203 additions and 119 deletions

View File

@ -16,11 +16,17 @@
package org.springframework.boot.actuate.endpoint;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint.EnvironmentDescriptor;
import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint.EnvironmentDescriptor.PropertySourceDescriptor;
import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint.EnvironmentDescriptor.PropertySourceDescriptor.PropertyValueDescriptor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.origin.OriginLookup;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
@ -41,7 +47,7 @@ import org.springframework.core.env.StandardEnvironment;
* @author Madhura Bhave
*/
@ConfigurationProperties(prefix = "endpoints.env")
public class EnvironmentEndpoint extends AbstractEndpoint<Map<String, Object>> {
public class EnvironmentEndpoint extends AbstractEndpoint<EnvironmentDescriptor> {
private final Sanitizer sanitizer = new Sanitizer();
@ -57,28 +63,35 @@ public class EnvironmentEndpoint extends AbstractEndpoint<Map<String, Object>> {
}
@Override
public Map<String, Object> invoke() {
Map<String, Object> result = new LinkedHashMap<>();
result.put("profiles", getEnvironment().getActiveProfiles());
public EnvironmentDescriptor invoke() {
PropertyResolver resolver = getResolver();
for (Entry<String, PropertySource<?>> entry : getPropertySourcesAsMap()
.entrySet()) {
PropertySource<?> source = entry.getValue();
String sourceName = entry.getKey();
List<PropertySourceDescriptor> propertySources = new ArrayList<PropertySourceDescriptor>();
getPropertySourcesAsMap().forEach((sourceName, source) -> {
if (source instanceof EnumerablePropertySource) {
EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) source;
Map<String, Object> properties = new LinkedHashMap<>();
for (String name : enumerable.getPropertyNames()) {
Object resolved = resolver.getProperty(name, Object.class);
properties.put(name, sanitize(name, resolved));
}
properties = postProcessSourceProperties(sourceName, properties);
if (properties != null) {
result.put(sourceName, properties);
}
propertySources.add(describeSource(sourceName,
(EnumerablePropertySource<?>) source, resolver));
}
});
return new EnvironmentDescriptor(
Arrays.asList(getEnvironment().getActiveProfiles()), propertySources);
}
private PropertySourceDescriptor describeSource(String sourceName,
EnumerablePropertySource<?> source, PropertyResolver resolver) {
Map<String, PropertyValueDescriptor> properties = new LinkedHashMap<>();
for (String name : source.getPropertyNames()) {
properties.put(name, describeValueOf(name, source, resolver));
}
return result;
return new PropertySourceDescriptor(sourceName, properties);
}
private PropertyValueDescriptor describeValueOf(String name,
EnumerablePropertySource<?> source, PropertyResolver resolver) {
Object resolved = resolver.getProperty(name, Object.class);
@SuppressWarnings("unchecked")
String origin = (source instanceof OriginLookup)
? ((OriginLookup<Object>) source).getOrigin(name).toString() : null;
return new PropertyValueDescriptor(sanitize(name, resolved), origin);
}
public PropertyResolver getResolver() {
@ -125,19 +138,6 @@ public class EnvironmentEndpoint extends AbstractEndpoint<Map<String, Object>> {
return this.sanitizer.sanitize(name, object);
}
/**
* Apply any post processing to source data before it is added.
* @param sourceName the source name
* @param properties the properties
* @return the post-processed properties or {@code null} if the source should not be
* added
* @since 1.4.0
*/
protected Map<String, Object> postProcessSourceProperties(String sourceName,
Map<String, Object> properties) {
return properties;
}
/**
* {@link PropertySourcesPropertyResolver} that sanitizes sensitive placeholders if
* present.
@ -166,4 +166,77 @@ public class EnvironmentEndpoint extends AbstractEndpoint<Map<String, Object>> {
}
/**
* A description of an {@link Environment}.
*/
static final class EnvironmentDescriptor {
private final List<String> activeProfiles;
private final List<PropertySourceDescriptor> propertySources;
private EnvironmentDescriptor(List<String> activeProfiles,
List<PropertySourceDescriptor> propertySources) {
this.activeProfiles = activeProfiles;
this.propertySources = propertySources;
}
public List<String> getActiveProfiles() {
return this.activeProfiles;
}
public List<PropertySourceDescriptor> getPropertySources() {
return this.propertySources;
}
/**
* A description of a {@link PropertySource}.
*/
static final class PropertySourceDescriptor {
private final String name;
private final Map<String, PropertyValueDescriptor> properties;
private PropertySourceDescriptor(String name,
Map<String, PropertyValueDescriptor> properties) {
this.name = name;
this.properties = properties;
}
public String getName() {
return this.name;
}
public Map<String, PropertyValueDescriptor> getProperties() {
return this.properties;
}
/**
* A description of a property's value, including its origin if available.
*/
static final class PropertyValueDescriptor {
private final Object value;
private final String origin;
private PropertyValueDescriptor(Object value, String origin) {
this.value = value;
this.origin = origin;
}
public Object getValue() {
return this.value;
}
public String getOrigin() {
return this.origin;
}
}
}
}
}

View File

@ -23,6 +23,9 @@ import java.util.Map;
import org.junit.After;
import org.junit.Test;
import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint.EnvironmentDescriptor;
import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint.EnvironmentDescriptor.PropertySourceDescriptor;
import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint.EnvironmentDescriptor.PropertySourceDescriptor.PropertyValueDescriptor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
@ -42,6 +45,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Nicolas Lejeune
* @author Stephane Nicoll
* @author Madhura Bhave
* @author Andy Wilkinson
*/
public class EnvironmentEndpointTests extends AbstractEndpointTests<EnvironmentEndpoint> {
@ -56,13 +60,14 @@ public class EnvironmentEndpointTests extends AbstractEndpointTests<EnvironmentE
}
@Test
public void invoke() throws Exception {
assertThat(getEndpointBean().invoke()).isNotEmpty();
public void invoke() {
EnvironmentDescriptor env = getEndpointBean().invoke();
assertThat(env.getActiveProfiles()).isEmpty();
assertThat(env.getPropertySources()).hasSize(2);
}
@SuppressWarnings("unchecked")
@Test
public void testCompositeSource() throws Exception {
public void testCompositeSource() {
EnvironmentEndpoint report = getEndpointBean();
CompositePropertySource source = new CompositePropertySource("composite");
source.addPropertySource(new MapPropertySource("one",
@ -70,86 +75,86 @@ public class EnvironmentEndpointTests extends AbstractEndpointTests<EnvironmentE
source.addPropertySource(new MapPropertySource("two",
Collections.singletonMap("foo", (Object) "spam")));
this.context.getEnvironment().getPropertySources().addFirst(source);
Map<String, Object> env = report.invoke();
assertThat(((Map<String, Object>) env.get("composite:one")).get("foo"))
EnvironmentDescriptor env = report.invoke();
assertThat(getSource("composite:one", env).getProperties().get("foo").getValue())
.isEqualTo("bar");
}
@SuppressWarnings("unchecked")
@Test
public void testKeySanitization() throws Exception {
public void testKeySanitization() {
System.setProperty("dbPassword", "123456");
System.setProperty("apiKey", "123456");
System.setProperty("mySecret", "123456");
System.setProperty("myCredentials", "123456");
System.setProperty("VCAP_SERVICES", "123456");
EnvironmentEndpoint report = getEndpointBean();
Map<String, Object> env = report.invoke();
Map<String, Object> systemProperties = (Map<String, Object>) env
.get("systemProperties");
assertThat(systemProperties.get("dbPassword")).isEqualTo("******");
assertThat(systemProperties.get("apiKey")).isEqualTo("******");
assertThat(systemProperties.get("mySecret")).isEqualTo("******");
assertThat(systemProperties.get("myCredentials")).isEqualTo("******");
assertThat(systemProperties.get("VCAP_SERVICES")).isEqualTo("******");
clearSystemProperties("dbPassword", "apiKey", "mySecret", "myCredentials");
EnvironmentDescriptor env = report.invoke();
Map<String, PropertyValueDescriptor> systemProperties = getSource(
"systemProperties", env).getProperties();
assertThat(systemProperties.get("dbPassword").getValue()).isEqualTo("******");
assertThat(systemProperties.get("apiKey").getValue()).isEqualTo("******");
assertThat(systemProperties.get("mySecret").getValue()).isEqualTo("******");
assertThat(systemProperties.get("myCredentials").getValue()).isEqualTo("******");
assertThat(systemProperties.get("VCAP_SERVICES").getValue()).isEqualTo("******");
clearSystemProperties("dbPassword", "apiKey", "mySecret", "myCredentials",
"VCAP_SERVICES");
}
@SuppressWarnings("unchecked")
@Test
public void testKeySanitizationCredentialsPattern() throws Exception {
public void testKeySanitizationCredentialsPattern() {
System.setProperty("my.services.amqp-free.credentials.uri", "123456");
System.setProperty("credentials.http_api_uri", "123456");
System.setProperty("my.services.cleardb-free.credentials", "123456");
System.setProperty("foo.mycredentials.uri", "123456");
EnvironmentEndpoint report = getEndpointBean();
Map<String, Object> env = report.invoke();
Map<String, Object> systemProperties = (Map<String, Object>) env
.get("systemProperties");
assertThat(systemProperties.get("my.services.amqp-free.credentials.uri"))
EnvironmentDescriptor env = report.invoke();
Map<String, PropertyValueDescriptor> systemProperties = getSource(
"systemProperties", env).getProperties();
assertThat(
systemProperties.get("my.services.amqp-free.credentials.uri").getValue())
.isEqualTo("******");
assertThat(systemProperties.get("credentials.http_api_uri").getValue())
.isEqualTo("******");
assertThat(systemProperties.get("credentials.http_api_uri")).isEqualTo("******");
assertThat(systemProperties.get("my.services.cleardb-free.credentials"))
assertThat(
systemProperties.get("my.services.cleardb-free.credentials").getValue())
.isEqualTo("******");
assertThat(systemProperties.get("foo.mycredentials.uri").getValue())
.isEqualTo("******");
assertThat(systemProperties.get("foo.mycredentials.uri")).isEqualTo("******");
clearSystemProperties("my.services.amqp-free.credentials.uri",
"credentials.http_api_uri", "my.services.cleardb-free.credentials",
"foo.mycredentials.uri");
}
@SuppressWarnings("unchecked")
@Test
public void testKeySanitizationWithCustomKeys() throws Exception {
public void testKeySanitizationWithCustomKeys() {
System.setProperty("dbPassword", "123456");
System.setProperty("apiKey", "123456");
EnvironmentEndpoint report = getEndpointBean();
report.setKeysToSanitize("key");
Map<String, Object> env = report.invoke();
Map<String, Object> systemProperties = (Map<String, Object>) env
.get("systemProperties");
assertThat(systemProperties.get("dbPassword")).isEqualTo("123456");
assertThat(systemProperties.get("apiKey")).isEqualTo("******");
EnvironmentDescriptor env = report.invoke();
Map<String, PropertyValueDescriptor> systemProperties = getSource(
"systemProperties", env).getProperties();
assertThat(systemProperties.get("dbPassword").getValue()).isEqualTo("123456");
assertThat(systemProperties.get("apiKey").getValue()).isEqualTo("******");
clearSystemProperties("dbPassword", "apiKey");
}
@SuppressWarnings("unchecked")
@Test
public void testKeySanitizationWithCustomPattern() throws Exception {
public void testKeySanitizationWithCustomPattern() {
System.setProperty("dbPassword", "123456");
System.setProperty("apiKey", "123456");
EnvironmentEndpoint report = getEndpointBean();
report.setKeysToSanitize(".*pass.*");
Map<String, Object> env = report.invoke();
Map<String, Object> systemProperties = (Map<String, Object>) env
.get("systemProperties");
assertThat(systemProperties.get("dbPassword")).isEqualTo("******");
assertThat(systemProperties.get("apiKey")).isEqualTo("123456");
EnvironmentDescriptor env = report.invoke();
Map<String, PropertyValueDescriptor> systemProperties = getSource(
"systemProperties", env).getProperties();
assertThat(systemProperties.get("dbPassword").getValue()).isEqualTo("******");
assertThat(systemProperties.get("apiKey").getValue()).isEqualTo("123456");
clearSystemProperties("dbPassword", "apiKey");
}
@SuppressWarnings("unchecked")
@Test
public void testKeySanitizationWithCustomKeysByEnvironment() throws Exception {
public void testKeySanitizationWithCustomKeysByEnvironment() {
this.context = new AnnotationConfigApplicationContext();
TestPropertyValues.of("endpoints.env.keys-to-sanitize: key")
.applyTo(this.context);
@ -158,17 +163,16 @@ public class EnvironmentEndpointTests extends AbstractEndpointTests<EnvironmentE
System.setProperty("dbPassword", "123456");
System.setProperty("apiKey", "123456");
EnvironmentEndpoint report = getEndpointBean();
Map<String, Object> env = report.invoke();
Map<String, Object> systemProperties = (Map<String, Object>) env
.get("systemProperties");
assertThat(systemProperties.get("dbPassword")).isEqualTo("123456");
assertThat(systemProperties.get("apiKey")).isEqualTo("******");
EnvironmentDescriptor env = report.invoke();
Map<String, PropertyValueDescriptor> systemProperties = getSource(
"systemProperties", env).getProperties();
assertThat(systemProperties.get("dbPassword").getValue()).isEqualTo("123456");
assertThat(systemProperties.get("apiKey").getValue()).isEqualTo("******");
clearSystemProperties("dbPassword", "apiKey");
}
@SuppressWarnings("unchecked")
@Test
public void testKeySanitizationWithCustomPatternByEnvironment() throws Exception {
public void testKeySanitizationWithCustomPatternByEnvironment() {
this.context = new AnnotationConfigApplicationContext();
TestPropertyValues.of("endpoints.env.keys-to-sanitize: .*pass.*")
.applyTo(this.context);
@ -177,18 +181,16 @@ public class EnvironmentEndpointTests extends AbstractEndpointTests<EnvironmentE
System.setProperty("dbPassword", "123456");
System.setProperty("apiKey", "123456");
EnvironmentEndpoint report = getEndpointBean();
Map<String, Object> env = report.invoke();
Map<String, Object> systemProperties = (Map<String, Object>) env
.get("systemProperties");
assertThat(systemProperties.get("dbPassword")).isEqualTo("******");
assertThat(systemProperties.get("apiKey")).isEqualTo("123456");
EnvironmentDescriptor env = report.invoke();
Map<String, PropertyValueDescriptor> systemProperties = getSource(
"systemProperties", env).getProperties();
assertThat(systemProperties.get("dbPassword").getValue()).isEqualTo("******");
assertThat(systemProperties.get("apiKey").getValue()).isEqualTo("123456");
clearSystemProperties("dbPassword", "apiKey");
}
@SuppressWarnings("unchecked")
@Test
public void testKeySanitizationWithCustomPatternAndKeyByEnvironment()
throws Exception {
public void testKeySanitizationWithCustomPatternAndKeyByEnvironment() {
this.context = new AnnotationConfigApplicationContext();
TestPropertyValues.of("endpoints.env.keys-to-sanitize: .*pass.*, key")
.applyTo(this.context);
@ -197,44 +199,43 @@ public class EnvironmentEndpointTests extends AbstractEndpointTests<EnvironmentE
System.setProperty("dbPassword", "123456");
System.setProperty("apiKey", "123456");
EnvironmentEndpoint report = getEndpointBean();
Map<String, Object> env = report.invoke();
Map<String, Object> systemProperties = (Map<String, Object>) env
.get("systemProperties");
assertThat(systemProperties.get("dbPassword")).isEqualTo("******");
assertThat(systemProperties.get("apiKey")).isEqualTo("******");
EnvironmentDescriptor env = report.invoke();
Map<String, PropertyValueDescriptor> systemProperties = getSource(
"systemProperties", env).getProperties();
assertThat(systemProperties.get("dbPassword").getValue()).isEqualTo("******");
assertThat(systemProperties.get("apiKey").getValue()).isEqualTo("******");
clearSystemProperties("dbPassword", "apiKey");
}
@SuppressWarnings("unchecked")
@Test
public void propertyWithPlaceholderResolved() throws Exception {
public void propertyWithPlaceholderResolved() {
this.context = new AnnotationConfigApplicationContext();
TestPropertyValues.of("my.foo: ${bar.blah}", "bar.blah: hello")
.applyTo(this.context);
this.context.register(Config.class);
this.context.refresh();
EnvironmentEndpoint report = getEndpointBean();
Map<String, Object> env = report.invoke();
Map<String, Object> testProperties = (Map<String, Object>) env.get("test");
assertThat(testProperties.get("my.foo")).isEqualTo("hello");
EnvironmentDescriptor env = report.invoke();
Map<String, PropertyValueDescriptor> testProperties = getSource("test", env)
.getProperties();
assertThat(testProperties.get("my.foo").getValue()).isEqualTo("hello");
}
@SuppressWarnings("unchecked")
@Test
public void propertyWithPlaceholderNotResolved() throws Exception {
public void propertyWithPlaceholderNotResolved() {
this.context = new AnnotationConfigApplicationContext();
TestPropertyValues.of("my.foo: ${bar.blah}").applyTo(this.context);
this.context.register(Config.class);
this.context.refresh();
EnvironmentEndpoint report = getEndpointBean();
Map<String, Object> env = report.invoke();
Map<String, Object> testProperties = (Map<String, Object>) env.get("test");
assertThat(testProperties.get("my.foo")).isEqualTo("${bar.blah}");
EnvironmentDescriptor env = report.invoke();
Map<String, PropertyValueDescriptor> testProperties = getSource("test", env)
.getProperties();
assertThat(testProperties.get("my.foo").getValue()).isEqualTo("${bar.blah}");
}
@SuppressWarnings("unchecked")
@Test
public void propertyWithSensitivePlaceholderResolved() throws Exception {
public void propertyWithSensitivePlaceholderResolved() {
this.context = new AnnotationConfigApplicationContext();
TestPropertyValues
.of("my.foo: http://${bar.password}://hello", "bar.password: hello")
@ -242,29 +243,31 @@ public class EnvironmentEndpointTests extends AbstractEndpointTests<EnvironmentE
this.context.register(Config.class);
this.context.refresh();
EnvironmentEndpoint report = getEndpointBean();
Map<String, Object> env = report.invoke();
Map<String, Object> testProperties = (Map<String, Object>) env.get("test");
assertThat(testProperties.get("my.foo")).isEqualTo("http://******://hello");
EnvironmentDescriptor env = report.invoke();
Map<String, PropertyValueDescriptor> testProperties = getSource("test", env)
.getProperties();
assertThat(testProperties.get("my.foo").getValue())
.isEqualTo("http://******://hello");
}
@SuppressWarnings("unchecked")
@Test
public void propertyWithSensitivePlaceholderNotResolved() throws Exception {
public void propertyWithSensitivePlaceholderNotResolved() {
this.context = new AnnotationConfigApplicationContext();
TestPropertyValues.of("my.foo: http://${bar.password}://hello")
.applyTo(this.context);
this.context.register(Config.class);
this.context.refresh();
EnvironmentEndpoint report = getEndpointBean();
Map<String, Object> env = report.invoke();
Map<String, Object> testProperties = (Map<String, Object>) env.get("test");
assertThat(testProperties.get("my.foo"))
EnvironmentDescriptor env = report.invoke();
Map<String, PropertyValueDescriptor> testProperties = getSource("test", env)
.getProperties();
assertThat(testProperties.get("my.foo").getValue())
.isEqualTo("http://${bar.password}://hello");
}
@Test
@SuppressWarnings("unchecked")
public void propertyWithTypeOtherThanStringShouldNotFail() throws Exception {
public void propertyWithTypeOtherThanStringShouldNotFail() {
this.context = new AnnotationConfigApplicationContext();
MutablePropertySources propertySources = this.context.getEnvironment()
.getPropertySources();
@ -274,9 +277,11 @@ public class EnvironmentEndpointTests extends AbstractEndpointTests<EnvironmentE
this.context.register(Config.class);
this.context.refresh();
EnvironmentEndpoint report = getEndpointBean();
Map<String, Object> env = report.invoke();
Map<String, Object> testProperties = (Map<String, Object>) env.get("test");
Map<String, String> foo = (Map<String, String>) testProperties.get("foo");
EnvironmentDescriptor env = report.invoke();
Map<String, PropertyValueDescriptor> testProperties = getSource("test", env)
.getProperties();
Map<String, String> foo = (Map<String, String>) testProperties.get("foo")
.getValue();
assertThat(foo.get("bar")).isEqualTo("baz");
}
@ -286,6 +291,12 @@ public class EnvironmentEndpointTests extends AbstractEndpointTests<EnvironmentE
}
}
private PropertySourceDescriptor getSource(String name,
EnvironmentDescriptor descriptor) {
return descriptor.getPropertySources().stream()
.filter((source) -> name.equals(source.getName())).findFirst().get();
}
@Configuration
@EnableConfigurationProperties
public static class Config {