From c2c6f49cbc170a4b1b8bfe40ddcbbaa8f86231cd Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Tue, 26 Sep 2017 17:58:58 +0200 Subject: [PATCH] Improve output of `/application/env/{propertyName}` This commit changes the output of a single property to mention the actual value in the environment as well as the property source that contributed to the value. Closes gh-10178 --- ...ronmentEndpointAutoConfigurationTests.java | 4 +- .../boot/actuate/env/EnvironmentEndpoint.java | 211 +++++++++++--- .../actuate/env/EnvironmentEndpointTests.java | 275 +++++++++++------- ...nvironmentEndpointWebIntegrationTests.java | 19 +- .../source/ConfigurationPropertySources.java | 12 + 5 files changed, 367 insertions(+), 154 deletions(-) diff --git a/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/env/EnvironmentEndpointAutoConfigurationTests.java b/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/env/EnvironmentEndpointAutoConfigurationTests.java index d5d676730b..2f5921b530 100644 --- a/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/env/EnvironmentEndpointAutoConfigurationTests.java +++ b/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/env/EnvironmentEndpointAutoConfigurationTests.java @@ -22,8 +22,8 @@ import org.junit.Test; import org.springframework.boot.actuate.env.EnvironmentEndpoint; import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentDescriptor; -import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentDescriptor.PropertySourceDescriptor; -import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentDescriptor.PropertySourceDescriptor.PropertyValueDescriptor; +import org.springframework.boot.actuate.env.EnvironmentEndpoint.PropertySourceDescriptor; +import org.springframework.boot.actuate.env.EnvironmentEndpoint.PropertyValueDescriptor; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/env/EnvironmentEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/env/EnvironmentEndpoint.java index 42b7a65a7a..0f63fc2e0d 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/env/EnvironmentEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/env/EnvironmentEndpoint.java @@ -25,14 +25,15 @@ import java.util.function.Predicate; import java.util.regex.Pattern; import java.util.stream.Stream; +import com.fasterxml.jackson.annotation.JsonInclude; + import org.springframework.boot.actuate.endpoint.Sanitizer; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.Selector; -import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentDescriptor.PropertySourceDescriptor; -import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentDescriptor.PropertySourceDescriptor.PropertyValueDescriptor; import org.springframework.boot.context.properties.bind.PlaceholdersResolver; import org.springframework.boot.context.properties.bind.PropertySourcesPlaceholdersResolver; +import org.springframework.boot.context.properties.source.ConfigurationPropertySources; import org.springframework.boot.origin.OriginLookup; import org.springframework.core.env.CompositePropertySource; import org.springframework.core.env.ConfigurableEnvironment; @@ -54,6 +55,7 @@ import org.springframework.web.bind.annotation.ResponseStatus; * @author Phillip Webb * @author Christian Dupuis * @author Madhura Bhave + * @author Stephane Nicoll * @since 2.0.0 */ @Endpoint(id = "env") @@ -80,8 +82,8 @@ public class EnvironmentEndpoint { } @ReadOperation - public EnvironmentDescriptor environmentEntry(@Selector String toMatch) { - return getEnvironmentDescriptor(toMatch::equals); + public EnvironmentEntryDescriptor environmentEntry(@Selector String toMatch) { + return getEnvironmentEntryDescriptor(toMatch); } private EnvironmentDescriptor getEnvironmentDescriptor( @@ -99,6 +101,46 @@ public class EnvironmentEndpoint { Arrays.asList(this.environment.getActiveProfiles()), propertySources); } + private EnvironmentEntryDescriptor getEnvironmentEntryDescriptor( + String propertyName) { + Map descriptors = getPropertySourceDescriptors( + propertyName); + PropertySummaryDescriptor summary = getPropertySummaryDescriptor(descriptors); + return new EnvironmentEntryDescriptor(summary, + Arrays.asList(this.environment.getActiveProfiles()), + toPropertySourceDescriptors(descriptors)); + } + + private List toPropertySourceDescriptors( + Map descriptors) { + List result = new ArrayList<>(); + for (Map.Entry entry : descriptors.entrySet()) { + result.add(new PropertySourceEntryDescriptor(entry.getKey(), entry.getValue())); + } + return result; + } + + private PropertySummaryDescriptor getPropertySummaryDescriptor( + Map descriptors) { + for (Map.Entry entry : descriptors.entrySet()) { + if (entry.getValue() != null) { + return new PropertySummaryDescriptor(entry.getKey(), + entry.getValue().getValue()); + } + } + return null; + } + + private Map getPropertySourceDescriptors( + String propertyName) { + Map propertySources = new LinkedHashMap<>(); + PlaceholdersResolver resolver = getResolver(); + getPropertySourcesAsMap().forEach((sourceName, source) -> + propertySources.put(sourceName, source.containsProperty(propertyName) ? + describeValueOf(propertyName, source, resolver) : null)); + return propertySources; + } + private PropertySourceDescriptor describeSource(String sourceName, EnumerablePropertySource source, PlaceholdersResolver resolver, Predicate namePredicate) { @@ -109,7 +151,7 @@ public class EnvironmentEndpoint { } private PropertyValueDescriptor describeValueOf(String name, - EnumerablePropertySource source, PlaceholdersResolver resolver) { + PropertySource source, PlaceholdersResolver resolver) { Object resolved = resolver.resolvePlaceholders(source.getProperty(name)); @SuppressWarnings("unchecked") String origin = (source instanceof OriginLookup) @@ -125,7 +167,9 @@ public class EnvironmentEndpoint { private Map> getPropertySourcesAsMap() { Map> map = new LinkedHashMap<>(); for (PropertySource source : getPropertySources()) { - extract("", map, source); + if (!ConfigurationPropertySources.isMainConfigurationPropertySource(source)) { + extract("", map, source); + } } return map; } @@ -208,54 +252,141 @@ public class EnvironmentEndpoint { return this.propertySources; } - /** - * A description of a {@link PropertySource}. - */ - public static final class PropertySourceDescriptor { + } - private final String name; + /** + * A description of an entry of the {@link Environment}. + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + public static final class EnvironmentEntryDescriptor { - private final Map properties; + private final PropertySummaryDescriptor property; - private PropertySourceDescriptor(String name, - Map properties) { - this.name = name; - this.properties = properties; - } + private final List activeProfiles; - public String getName() { - return this.name; - } + private final List propertySources; - public Map getProperties() { - return this.properties; - } + private EnvironmentEntryDescriptor(PropertySummaryDescriptor property, + List activeProfiles, + List propertySources) { + this.property = property; + this.activeProfiles = activeProfiles; + this.propertySources = propertySources; + } - /** - * A description of a property's value, including its origin if available. - */ - public static final class PropertyValueDescriptor { + public PropertySummaryDescriptor getProperty() { + return this.property; + } - private final Object value; + public List getActiveProfiles() { + return this.activeProfiles; + } - private final String origin; + public List getPropertySources() { + return this.propertySources; + } - private PropertyValueDescriptor(Object value, String origin) { - this.value = value; - this.origin = origin; - } + } - public Object getValue() { - return this.value; - } + /** + * A summary of a particular entry of the {@link Environment}. + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + public static final class PropertySummaryDescriptor { - public String getOrigin() { - return this.origin; - } + private final String source; - } + private final Object value; + + public PropertySummaryDescriptor(String source, Object value) { + this.source = source; + this.value = value; + } + + public String getSource() { + return this.source; + } + + public Object getValue() { + return this.value; + } + + } + + /** + * A description of a particular entry of {@link PropertySource}. + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + public static final class PropertySourceEntryDescriptor { + + private final String name; + + private final PropertyValueDescriptor property; + + private PropertySourceEntryDescriptor(String name, + PropertyValueDescriptor property) { + this.name = name; + this.property = property; + } + public String getName() { + return this.name; } + + public PropertyValueDescriptor getProperty() { + return this.property; + } + + } + + /** + * A description of a {@link PropertySource}. + */ + public 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. + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + public 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/env/EnvironmentEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/env/EnvironmentEndpointTests.java index 8a3d165207..d1c677657d 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/env/EnvironmentEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/env/EnvironmentEndpointTests.java @@ -17,23 +17,25 @@ package org.springframework.boot.actuate.env; import java.util.Collections; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; import org.junit.After; import org.junit.Test; import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentDescriptor; -import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentDescriptor.PropertySourceDescriptor; -import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentDescriptor.PropertySourceDescriptor.PropertyValueDescriptor; +import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentEntryDescriptor; +import org.springframework.boot.actuate.env.EnvironmentEndpoint.PropertySourceDescriptor; +import org.springframework.boot.actuate.env.EnvironmentEndpoint.PropertySourceEntryDescriptor; +import org.springframework.boot.actuate.env.EnvironmentEndpoint.PropertyValueDescriptor; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.util.TestPropertyValues; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.CompositePropertySource; +import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; import org.springframework.core.env.MapPropertySource; -import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.StandardEnvironment; import static org.assertj.core.api.Assertions.assertThat; @@ -57,184 +59,243 @@ public class EnvironmentEndpointTests { @Test public void basicResponse() { - EnvironmentDescriptor env = new EnvironmentEndpoint(new StandardEnvironment()) + ConfigurableEnvironment environment = emptyEnvironment(); + environment.getPropertySources().addLast( + singleKeyPropertySource("one", "my.key", "first")); + environment.getPropertySources().addLast( + singleKeyPropertySource("two", "my.key", "second")); + EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment) .environment(null); - assertThat(env.getActiveProfiles()).isEmpty(); - assertThat(env.getPropertySources()).hasSize(2); + assertThat(descriptor.getActiveProfiles()).isEmpty(); + Map sources = propertySources(descriptor); + assertThat(sources.keySet()).containsExactly("one", "two"); + assertThat(sources.get("one").getProperties()).containsOnlyKeys("my.key"); + assertThat(sources.get("two").getProperties()).containsOnlyKeys("my.key"); } @Test public void compositeSourceIsHandledCorrectly() { - StandardEnvironment environment = new StandardEnvironment(); + ConfigurableEnvironment environment = emptyEnvironment(); CompositePropertySource source = new CompositePropertySource("composite"); source.addPropertySource( new MapPropertySource("one", Collections.singletonMap("foo", "bar"))); source.addPropertySource( new MapPropertySource("two", Collections.singletonMap("foo", "spam"))); environment.getPropertySources().addFirst(source); - EnvironmentDescriptor env = new EnvironmentEndpoint(environment) + EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment) .environment(null); - assertThat(getSource("composite:one", env).getProperties().get("foo").getValue()) + Map sources = propertySources(descriptor); + assertThat(sources.keySet()).containsExactly("composite:one", "composite:two"); + assertThat(sources.get("composite:one").getProperties().get("foo").getValue()) .isEqualTo("bar"); - assertThat(getSource("composite:two", env).getProperties().get("foo").getValue()) + assertThat(sources.get("composite:two").getProperties().get("foo").getValue()) .isEqualTo("spam"); } @Test public void sensitiveKeysHaveTheirValuesSanitized() { - System.setProperty("dbPassword", "123456"); - System.setProperty("apiKey", "123456"); - System.setProperty("mySecret", "123456"); - System.setProperty("myCredentials", "123456"); - System.setProperty("VCAP_SERVICES", "123456"); - EnvironmentDescriptor env = new EnvironmentEndpoint(new StandardEnvironment()) - .environment(null); - 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"); + TestPropertyValues.of("dbPassword=123456", "apiKey=123456", "mySecret=123456", + "myCredentials=123456", "VCAP_SERVICES=123456" + ).applyToSystemProperties(() -> { + EnvironmentDescriptor descriptor = new EnvironmentEndpoint( + new StandardEnvironment()).environment(null); + Map systemProperties = propertySources( + descriptor).get("systemProperties").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("******"); + return null; + }); } @Test public void sensitiveKeysMatchingCredentialsPatternHaveTheirValuesSanitized() { - 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"); - EnvironmentDescriptor env = new EnvironmentEndpoint(new StandardEnvironment()) - .environment(null); - 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("my.services.cleardb-free.credentials").getValue()) - .isEqualTo("******"); - assertThat(systemProperties.get("foo.mycredentials.uri").getValue()) - .isEqualTo("******"); - clearSystemProperties("my.services.amqp-free.credentials.uri", - "credentials.http_api_uri", "my.services.cleardb-free.credentials", - "foo.mycredentials.uri"); + TestPropertyValues.of("my.services.amqp-free.credentials.uri=123456", + "credentials.http_api_uri=123456", + "my.services.cleardb-free.credentials=123456", + "foo.mycredentials.uri=123456").applyToSystemProperties(() -> { + EnvironmentDescriptor descriptor = new EnvironmentEndpoint( + new StandardEnvironment()).environment(null); + Map systemProperties = propertySources( + descriptor).get("systemProperties").getProperties(); + assertThat( + systemProperties.get("my.services.amqp-free.credentials.uri").getValue()) + .isEqualTo("******"); + assertThat(systemProperties.get("credentials.http_api_uri").getValue()) + .isEqualTo("******"); + assertThat( + systemProperties.get("my.services.cleardb-free.credentials").getValue()) + .isEqualTo("******"); + assertThat(systemProperties.get("foo.mycredentials.uri").getValue()) + .isEqualTo("******"); + return null; + }); } @Test public void sensitiveKeysMatchingCustomNameHaveTheirValuesSanitized() { - System.setProperty("dbPassword", "123456"); - System.setProperty("apiKey", "123456"); - EnvironmentEndpoint endpoint = new EnvironmentEndpoint(new StandardEnvironment()); - endpoint.setKeysToSanitize("key"); - EnvironmentDescriptor env = endpoint.environment(null); - Map systemProperties = getSource( - "systemProperties", env).getProperties(); - assertThat(systemProperties.get("dbPassword").getValue()).isEqualTo("123456"); - assertThat(systemProperties.get("apiKey").getValue()).isEqualTo("******"); - clearSystemProperties("dbPassword", "apiKey"); + TestPropertyValues.of("dbPassword=123456", + "apiKey=123456").applyToSystemProperties(() -> { + EnvironmentEndpoint endpoint = new EnvironmentEndpoint(new StandardEnvironment()); + endpoint.setKeysToSanitize("key"); + EnvironmentDescriptor descriptor = endpoint.environment(null); + Map systemProperties = propertySources( + descriptor).get("systemProperties").getProperties(); + assertThat(systemProperties.get("dbPassword").getValue()).isEqualTo("123456"); + assertThat(systemProperties.get("apiKey").getValue()).isEqualTo("******"); + return null; + }); } @Test public void sensitiveKeysMatchingCustomPatternHaveTheirValuesSanitized() { - System.setProperty("dbPassword", "123456"); - System.setProperty("apiKey", "123456"); - EnvironmentEndpoint endpoint = new EnvironmentEndpoint(new StandardEnvironment()); - endpoint.setKeysToSanitize(".*pass.*"); - EnvironmentDescriptor env = endpoint.environment(null); - Map systemProperties = getSource( - "systemProperties", env).getProperties(); - assertThat(systemProperties.get("dbPassword").getValue()).isEqualTo("******"); - assertThat(systemProperties.get("apiKey").getValue()).isEqualTo("123456"); - clearSystemProperties("dbPassword", "apiKey"); + TestPropertyValues.of("dbPassword=123456", + "apiKey=123456").applyToSystemProperties(() -> { + EnvironmentEndpoint endpoint = new EnvironmentEndpoint(new StandardEnvironment()); + endpoint.setKeysToSanitize(".*pass.*"); + EnvironmentDescriptor descriptor = endpoint.environment(null); + Map systemProperties = propertySources( + descriptor).get("systemProperties").getProperties(); + assertThat(systemProperties.get("dbPassword").getValue()).isEqualTo("******"); + assertThat(systemProperties.get("apiKey").getValue()).isEqualTo("123456"); + return null; + }); } @Test public void propertyWithPlaceholderResolved() { - StandardEnvironment environment = new StandardEnvironment(); + ConfigurableEnvironment environment = emptyEnvironment(); TestPropertyValues.of("my.foo: ${bar.blah}", "bar.blah: hello") .applyTo(environment); - EnvironmentDescriptor env = new EnvironmentEndpoint(environment) + EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment) .environment(null); - assertThat(getSource("test", env).getProperties().get("my.foo").getValue()) - .isEqualTo("hello"); + assertThat(propertySources(descriptor).get("test").getProperties().get("my.foo") + .getValue()).isEqualTo("hello"); } @Test public void propertyWithPlaceholderNotResolved() { - StandardEnvironment environment = new StandardEnvironment(); + ConfigurableEnvironment environment = emptyEnvironment(); TestPropertyValues.of("my.foo: ${bar.blah}").applyTo(environment); - EnvironmentDescriptor env = new EnvironmentEndpoint(environment) + EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment) .environment(null); - assertThat(getSource("test", env).getProperties().get("my.foo").getValue()) - .isEqualTo("${bar.blah}"); + assertThat(propertySources(descriptor).get("test").getProperties().get("my.foo") + .getValue()).isEqualTo("${bar.blah}"); } @Test public void propertyWithSensitivePlaceholderResolved() { - StandardEnvironment environment = new StandardEnvironment(); + ConfigurableEnvironment environment = emptyEnvironment(); TestPropertyValues .of("my.foo: http://${bar.password}://hello", "bar.password: hello") .applyTo(environment); - EnvironmentDescriptor env = new EnvironmentEndpoint(environment) + EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment) .environment(null); - assertThat(getSource("test", env).getProperties().get("my.foo").getValue()) - .isEqualTo("http://******://hello"); + assertThat(propertySources(descriptor).get("test").getProperties().get("my.foo") + .getValue()).isEqualTo("http://******://hello"); } @Test public void propertyWithSensitivePlaceholderNotResolved() { - StandardEnvironment environment = new StandardEnvironment(); + ConfigurableEnvironment environment = emptyEnvironment(); TestPropertyValues.of("my.foo: http://${bar.password}://hello") .applyTo(environment); - EnvironmentDescriptor env = new EnvironmentEndpoint(environment) + EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment) .environment(null); - assertThat(getSource("test", env).getProperties().get("my.foo").getValue()) - .isEqualTo("http://${bar.password}://hello"); + assertThat(propertySources(descriptor).get("test").getProperties().get("my.foo") + .getValue()).isEqualTo("http://${bar.password}://hello"); } @Test @SuppressWarnings("unchecked") public void propertyWithTypeOtherThanStringShouldNotFail() { - StandardEnvironment environment = new StandardEnvironment(); - MutablePropertySources propertySources = environment.getPropertySources(); - Map source = new HashMap<>(); - source.put("foo", Collections.singletonMap("bar", "baz")); - propertySources.addFirst(new MapPropertySource("test", source)); - EnvironmentDescriptor env = new EnvironmentEndpoint(environment) + ConfigurableEnvironment environment = emptyEnvironment(); + environment.getPropertySources().addFirst(singleKeyPropertySource("test", "foo", + Collections.singletonMap("bar", "baz"))); + EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment) .environment(null); - Map testProperties = getSource("test", env) - .getProperties(); - Map foo = (Map) testProperties.get("foo") - .getValue(); + Map foo = (Map) propertySources(descriptor) + .get("test").getProperties().get("foo").getValue(); assertThat(foo.get("bar")).isEqualTo("baz"); } @Test public void propertyEntry() { + TestPropertyValues.of("my.foo=another").applyToSystemProperties(() -> { + StandardEnvironment environment = new StandardEnvironment(); + TestPropertyValues.of("my.foo=bar", "my.foo2=bar2").applyTo(environment, + TestPropertyValues.Type.MAP, "test"); + EnvironmentEntryDescriptor descriptor = new EnvironmentEndpoint(environment) + .environmentEntry("my.foo"); + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getProperty()).isNotNull(); + assertThat(descriptor.getProperty().getSource()).isEqualTo("test"); + assertThat(descriptor.getProperty().getValue()).isEqualTo("bar"); + Map sources = propertySources(descriptor); + assertThat(sources.keySet()).containsExactly( + "test", "systemProperties", "systemEnvironment"); + assertPropertySourceEntryDescriptor(sources.get("test"), "bar", null); + assertPropertySourceEntryDescriptor(sources.get("systemProperties"), "another", null); + assertPropertySourceEntryDescriptor(sources.get("systemEnvironment"), null, null); + return null; + }); + } + + @Test + public void propertyEntryNotFound() { + ConfigurableEnvironment environment = emptyEnvironment(); + environment.getPropertySources().addFirst( + singleKeyPropertySource("test", "foo", "bar")); + EnvironmentEntryDescriptor descriptor = new EnvironmentEndpoint(environment) + .environmentEntry("does.not.exist"); + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getProperty()).isNull(); + Map sources = propertySources(descriptor); + assertThat(sources.keySet()).containsExactly( + "test"); + assertPropertySourceEntryDescriptor(sources.get("test"), null, null); + } + + private static ConfigurableEnvironment emptyEnvironment() { StandardEnvironment environment = new StandardEnvironment(); - TestPropertyValues.of("my.foo=bar", "my.foo2=bar2").applyTo(environment); - EnvironmentDescriptor env = new EnvironmentEndpoint(environment) - .environmentEntry("my.foo"); - assertThat(env).isNotNull(); - assertThat(getSource("test", env).getProperties().get("my.foo").getValue()) - .isEqualTo("bar"); + environment.getPropertySources().remove( + StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME); + environment.getPropertySources().remove( + StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME); + return environment; } - private void clearSystemProperties(String... properties) { - for (String property : properties) { - System.clearProperty(property); - } + private MapPropertySource singleKeyPropertySource(String name, String key, Object value) { + return new MapPropertySource(name, Collections.singletonMap(key, value)); } - private PropertySourceDescriptor getSource(String name, + private Map propertySources( EnvironmentDescriptor descriptor) { - return descriptor.getPropertySources().stream() - .filter((source) -> name.equals(source.getName())).findFirst().get(); + Map sources = new LinkedHashMap<>(); + descriptor.getPropertySources().forEach(d -> sources.put(d.getName(), d)); + return sources; + } + + private Map propertySources( + EnvironmentEntryDescriptor descriptor) { + Map sources = new LinkedHashMap<>(); + descriptor.getPropertySources().forEach(d -> sources.put(d.getName(), d)); + return sources; + } + + private void assertPropertySourceEntryDescriptor(PropertySourceEntryDescriptor actual, + Object value, String origin) { + assertThat(actual).isNotNull(); + if (value != null) { + assertThat(actual.getProperty().getValue()).isEqualTo(value); + assertThat(actual.getProperty().getOrigin()).isEqualTo(origin); + } + else { + assertThat(actual.getProperty()).isNull(); + } + } @Configuration diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/env/EnvironmentEndpointWebIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/env/EnvironmentEndpointWebIntegrationTests.java index 071334bce2..cbc4eddc26 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/env/EnvironmentEndpointWebIntegrationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/env/EnvironmentEndpointWebIntegrationTests.java @@ -53,7 +53,9 @@ public class EnvironmentEndpointWebIntegrationTests { @Test public void sub() throws Exception { client.get().uri("/application/env/foo").exchange().expectStatus().isOk() - .expectBody().jsonPath(forProperty("test", "foo")).isEqualTo("bar"); + .expectBody() + .jsonPath("property.source").isEqualTo("test") + .jsonPath("property.value").isEqualTo("bar"); } @Test @@ -75,8 +77,10 @@ public class EnvironmentEndpointWebIntegrationTests { context.getEnvironment().getPropertySources() .addFirst(new MapPropertySource("unresolved-placeholder", map)); client.get().uri("/application/env/my.foo").exchange().expectStatus().isOk() - .expectBody().jsonPath(forProperty("unresolved-placeholder", "my.foo")) - .isEqualTo("${my.bar}"); + .expectBody() + .jsonPath("property.value").isEqualTo("${my.bar}") + .jsonPath(forPropertyEntry( + "unresolved-placeholder")).isEqualTo("${my.bar}"); } @Test @@ -87,8 +91,9 @@ public class EnvironmentEndpointWebIntegrationTests { context.getEnvironment().getPropertySources() .addFirst(new MapPropertySource("placeholder", map)); client.get().uri("/application/env/my.foo").exchange().expectStatus().isOk() - .expectBody().jsonPath(forProperty("placeholder", "my.foo")) - .isEqualTo("******"); + .expectBody() + .jsonPath("property.value").isEqualTo("******") + .jsonPath(forPropertyEntry("placeholder")).isEqualTo("******"); } @Test @@ -123,6 +128,10 @@ public class EnvironmentEndpointWebIntegrationTests { + "'].value"; } + private String forPropertyEntry(String source) { + return "propertySources[?(@.name=='" + source + "')].property.value"; + } + @Configuration static class TestConfiguration { diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySources.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySources.java index d8112bcc71..662c8cda88 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySources.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySources.java @@ -45,6 +45,18 @@ public final class ConfigurationPropertySources { private ConfigurationPropertySources() { } + /** + * Determines if the specific {@link PropertySource} is the + * {@link ConfigurationPropertySource} that was {@link #attach(Environment) attached} + * to the {@link Environment}. + * @param propertySource the property source to test + * @return {@code true} if this is the attached {@link ConfigurationPropertySource} + */ + public static boolean isMainConfigurationPropertySource( + PropertySource propertySource) { + return ATTACHED_PROPERTY_SOURCE_NAME.equals(propertySource.getName()); + } + /** * Attach a {@link ConfigurationPropertySource} support to the specified * {@link Environment}. Adapts each {@link PropertySource} managed by the environment