diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/EnvironmentEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/EnvironmentEndpoint.java index d34455cf52e..e4b2892139d 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/EnvironmentEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/EnvironmentEndpoint.java @@ -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> { +public class EnvironmentEndpoint extends AbstractEndpoint { private final Sanitizer sanitizer = new Sanitizer(); @@ -57,28 +63,35 @@ public class EnvironmentEndpoint extends AbstractEndpoint> { } @Override - public Map invoke() { - Map result = new LinkedHashMap<>(); - result.put("profiles", getEnvironment().getActiveProfiles()); + public EnvironmentDescriptor invoke() { PropertyResolver resolver = getResolver(); - for (Entry> entry : getPropertySourcesAsMap() - .entrySet()) { - PropertySource source = entry.getValue(); - String sourceName = entry.getKey(); + List propertySources = new ArrayList(); + getPropertySourcesAsMap().forEach((sourceName, source) -> { if (source instanceof EnumerablePropertySource) { - EnumerablePropertySource enumerable = (EnumerablePropertySource) source; - Map 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 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) source).getOrigin(name).toString() : null; + return new PropertyValueDescriptor(sanitize(name, resolved), origin); } public PropertyResolver getResolver() { @@ -125,19 +138,6 @@ public class EnvironmentEndpoint extends AbstractEndpoint> { 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 postProcessSourceProperties(String sourceName, - Map properties) { - return properties; - } - /** * {@link PropertySourcesPropertyResolver} that sanitizes sensitive placeholders if * present. @@ -166,4 +166,77 @@ public class EnvironmentEndpoint extends AbstractEndpoint> { } + /** + * A description of an {@link Environment}. + */ + static final class EnvironmentDescriptor { + + private final List activeProfiles; + + private final List propertySources; + + private EnvironmentDescriptor(List activeProfiles, + List propertySources) { + this.activeProfiles = activeProfiles; + this.propertySources = propertySources; + } + + public List getActiveProfiles() { + return this.activeProfiles; + } + + public List getPropertySources() { + return this.propertySources; + } + + /** + * A description of a {@link PropertySource}. + */ + static final class PropertySourceDescriptor { + + private final String name; + + private final Map properties; + + private PropertySourceDescriptor(String name, + Map properties) { + this.name = name; + this.properties = properties; + } + + public String getName() { + return this.name; + } + + public Map 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; + } + + } + + } + } + } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/EnvironmentEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/EnvironmentEndpointTests.java index 9577dadc267..240a186b2e8 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/EnvironmentEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/EnvironmentEndpointTests.java @@ -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 { @@ -56,13 +60,14 @@ public class EnvironmentEndpointTests extends AbstractEndpointTests env = report.invoke(); - assertThat(((Map) 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 env = report.invoke(); - Map systemProperties = (Map) 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 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 env = report.invoke(); - Map systemProperties = (Map) env - .get("systemProperties"); - assertThat(systemProperties.get("my.services.amqp-free.credentials.uri")) + EnvironmentDescriptor env = report.invoke(); + Map 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 env = report.invoke(); - Map systemProperties = (Map) env - .get("systemProperties"); - assertThat(systemProperties.get("dbPassword")).isEqualTo("123456"); - assertThat(systemProperties.get("apiKey")).isEqualTo("******"); + EnvironmentDescriptor env = report.invoke(); + Map 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 env = report.invoke(); - Map systemProperties = (Map) env - .get("systemProperties"); - assertThat(systemProperties.get("dbPassword")).isEqualTo("******"); - assertThat(systemProperties.get("apiKey")).isEqualTo("123456"); + EnvironmentDescriptor env = report.invoke(); + Map 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 env = report.invoke(); - Map systemProperties = (Map) env - .get("systemProperties"); - assertThat(systemProperties.get("dbPassword")).isEqualTo("123456"); - assertThat(systemProperties.get("apiKey")).isEqualTo("******"); + EnvironmentDescriptor env = report.invoke(); + Map 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 env = report.invoke(); - Map systemProperties = (Map) env - .get("systemProperties"); - assertThat(systemProperties.get("dbPassword")).isEqualTo("******"); - assertThat(systemProperties.get("apiKey")).isEqualTo("123456"); + EnvironmentDescriptor env = report.invoke(); + Map 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 env = report.invoke(); - Map systemProperties = (Map) env - .get("systemProperties"); - assertThat(systemProperties.get("dbPassword")).isEqualTo("******"); - assertThat(systemProperties.get("apiKey")).isEqualTo("******"); + EnvironmentDescriptor env = report.invoke(); + Map 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 env = report.invoke(); - Map testProperties = (Map) env.get("test"); - assertThat(testProperties.get("my.foo")).isEqualTo("hello"); + EnvironmentDescriptor env = report.invoke(); + Map 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 env = report.invoke(); - Map testProperties = (Map) env.get("test"); - assertThat(testProperties.get("my.foo")).isEqualTo("${bar.blah}"); + EnvironmentDescriptor env = report.invoke(); + Map 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 env = report.invoke(); - Map testProperties = (Map) env.get("test"); - assertThat(testProperties.get("my.foo")).isEqualTo("http://******://hello"); + EnvironmentDescriptor env = report.invoke(); + Map 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 env = report.invoke(); - Map testProperties = (Map) env.get("test"); - assertThat(testProperties.get("my.foo")) + EnvironmentDescriptor env = report.invoke(); + Map 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 env = report.invoke(); - Map testProperties = (Map) env.get("test"); - Map foo = (Map) testProperties.get("foo"); + EnvironmentDescriptor env = report.invoke(); + Map testProperties = getSource("test", env) + .getProperties(); + Map foo = (Map) testProperties.get("foo") + .getValue(); assertThat(foo.get("bar")).isEqualTo("baz"); } @@ -286,6 +291,12 @@ public class EnvironmentEndpointTests extends AbstractEndpointTests name.equals(source.getName())).findFirst().get(); + } + @Configuration @EnableConfigurationProperties public static class Config {