From cf8776b83c1bb37306096310f83a0e834ced19ca Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Thu, 27 Aug 2020 18:10:30 -0700 Subject: [PATCH] Expose originParents on actuator endpoints Update `ConfigurationPropertiesReportEndpoint` and `EnvironmentEndpoint` so that they expose `originParents` when they are available. Closes gh-23018 --- ...ConfigurationPropertiesReportEndpoint.java | 9 ++- .../boot/actuate/env/EnvironmentEndpoint.java | 20 +++--- ...gurationPropertiesReportEndpointTests.java | 63 +++++++++++++++++++ .../actuate/env/EnvironmentEndpointTests.java | 47 ++++++++++++++ 4 files changed, 129 insertions(+), 10 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java index c64c854aed..6b7affc4bf 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java @@ -60,6 +60,7 @@ import org.springframework.boot.context.properties.ConfigurationPropertiesBean; import org.springframework.boot.context.properties.ConstructorBinding; import org.springframework.boot.context.properties.source.ConfigurationProperty; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; +import org.springframework.boot.origin.Origin; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.core.KotlinDetector; @@ -292,10 +293,14 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext private Map getInput(String property, ConfigurationProperty candidate) { Map input = new LinkedHashMap<>(); - String origin = (candidate.getOrigin() != null) ? candidate.getOrigin().toString() : "none"; Object value = candidate.getValue(); - input.put("origin", origin); + Origin origin = Origin.from(candidate); + List originParents = Origin.parentsFrom(candidate); input.put("value", this.sanitizer.sanitize(property, value)); + input.put("origin", (origin != null) ? origin.toString() : "none"); + if (!originParents.isEmpty()) { + input.put("originParents", originParents.stream().map(Object::toString).toArray(String[]::new)); + } return input; } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/env/EnvironmentEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/env/EnvironmentEndpoint.java index c652e590ca..2ece6c9a72 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/env/EnvironmentEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/env/EnvironmentEndpoint.java @@ -142,15 +142,10 @@ public class EnvironmentEndpoint { private PropertyValueDescriptor describeValueOf(String name, PropertySource source, PlaceholdersResolver resolver) { Object resolved = resolver.resolvePlaceholders(source.getProperty(name)); - String origin = ((source instanceof OriginLookup) ? getOrigin((OriginLookup) source, name) : null); + Origin origin = ((source instanceof OriginLookup) ? ((OriginLookup) source).getOrigin(name) : null); return new PropertyValueDescriptor(sanitize(name, resolved), origin); } - private String getOrigin(OriginLookup lookup, String name) { - Origin origin = lookup.getOrigin(name); - return (origin != null) ? origin.toString() : null; - } - private PlaceholdersResolver getResolver() { return new PropertySourcesPlaceholdersSanitizingResolver(getPropertySources(), this.sanitizer); } @@ -353,9 +348,14 @@ public class EnvironmentEndpoint { private final String origin; - private PropertyValueDescriptor(Object value, String origin) { + private final String[] originParents; + + private PropertyValueDescriptor(Object value, Origin origin) { this.value = value; - this.origin = origin; + this.origin = (origin != null) ? origin.toString() : null; + List originParents = Origin.parentsFrom(origin); + this.originParents = originParents.isEmpty() ? null + : originParents.stream().map(Object::toString).toArray(String[]::new); } public Object getValue() { @@ -366,6 +366,10 @@ public class EnvironmentEndpoint { return this.origin; } + public String[] getOriginParents() { + return this.originParents; + } + } } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointTests.java index 24d6e0a916..0533ca00d6 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointTests.java @@ -34,12 +34,16 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConstructorBinding; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.bind.DefaultValue; +import org.springframework.boot.origin.Origin; +import org.springframework.boot.origin.OriginLookup; import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ContextConsumer; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; +import org.springframework.mock.env.MockPropertySource; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; @@ -281,6 +285,23 @@ class ConfigurationPropertiesReportEndpointTests { })); } + @Test + void originParents() { + this.contextRunner.withUserConfiguration(SensiblePropertiesConfiguration.class) + .withInitializer(this::initializeOriginParents).run(assertProperties("sensible", (properties) -> { + }, (inputs) -> { + Map stringInputs = (Map) inputs.get("string"); + String[] originParents = (String[]) stringInputs.get("originParents"); + assertThat(originParents).containsExactly("spring", "boot"); + })); + } + + private void initializeOriginParents(ConfigurableApplicationContext context) { + MockPropertySource propertySource = new OriginParentMockPropertySource(); + propertySource.setProperty("sensible.string", "spring"); + context.getEnvironment().getPropertySources().addFirst(propertySource); + } + private ContextConsumer assertProperties(String prefix, Consumer> properties) { return assertProperties(prefix, properties, (inputs) -> { @@ -310,6 +331,38 @@ class ConfigurationPropertiesReportEndpointTests { return prefix.equals(candidate); } + static class OriginParentMockPropertySource extends MockPropertySource implements OriginLookup { + + @Override + public Origin getOrigin(String key) { + return new MockOrigin(key, new MockOrigin("spring", new MockOrigin("boot", null))); + } + + } + + static class MockOrigin implements Origin { + + private final String value; + + private final MockOrigin parent; + + MockOrigin(String value, MockOrigin parent) { + this.value = value; + this.parent = parent; + } + + @Override + public Origin getParent() { + return this.parent; + } + + @Override + public String toString() { + return this.value; + } + + } + @Configuration(proxyBeanMethods = false) static class EndpointConfig { @@ -649,6 +702,8 @@ class ConfigurationPropertiesReportEndpointTests { @ConfigurationProperties("sensible") public static class SensibleProperties { + private String string; + private URI sensitiveUri = URI.create("http://user:password@localhost:8080"); private URI noPasswordUri = URI.create("http://user:@localhost:8080"); @@ -666,6 +721,14 @@ class ConfigurationPropertiesReportEndpointTests { this.listOfListItems.add(Collections.singletonList(new ListItem())); } + public void setString(String string) { + this.string = string; + } + + public String getString() { + return this.string; + } + public void setSensitiveUri(URI sensitiveUri) { this.sensitiveUri = sensitiveUri; } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/env/EnvironmentEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/env/EnvironmentEndpointTests.java index 76481d5d12..dfe90081c4 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/env/EnvironmentEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/env/EnvironmentEndpointTests.java @@ -29,6 +29,8 @@ import org.springframework.boot.actuate.env.EnvironmentEndpoint.PropertySourceDe 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.origin.Origin; +import org.springframework.boot.origin.OriginLookup; import org.springframework.boot.test.util.TestPropertyValues; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -37,6 +39,7 @@ import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.StandardEnvironment; +import org.springframework.mock.env.MockPropertySource; import static org.assertj.core.api.Assertions.assertThat; @@ -222,6 +225,18 @@ class EnvironmentEndpointTests { }); } + @Test + void originAndOriginParents() { + StandardEnvironment environment = new StandardEnvironment(); + OriginParentMockPropertySource propertySource = new OriginParentMockPropertySource(); + propertySource.setProperty("name", "test"); + environment.getPropertySources().addFirst(propertySource); + EnvironmentEntryDescriptor descriptor = new EnvironmentEndpoint(environment).environmentEntry("name"); + PropertySourceEntryDescriptor entryDescriptor = propertySources(descriptor).get("mockProperties"); + assertThat(entryDescriptor.getProperty().getOrigin()).isEqualTo("name"); + assertThat(entryDescriptor.getProperty().getOriginParents()).containsExactly("spring", "boot"); + } + @Test void propertyEntryNotFound() { ConfigurableEnvironment environment = emptyEnvironment(); @@ -302,6 +317,38 @@ class EnvironmentEndpointTests { } + static class OriginParentMockPropertySource extends MockPropertySource implements OriginLookup { + + @Override + public Origin getOrigin(String key) { + return new MockOrigin(key, new MockOrigin("spring", new MockOrigin("boot", null))); + } + + } + + static class MockOrigin implements Origin { + + private final String value; + + private final MockOrigin parent; + + MockOrigin(String value, MockOrigin parent) { + this.value = value; + this.parent = parent; + } + + @Override + public Origin getParent() { + return this.parent; + } + + @Override + public String toString() { + return this.value; + } + + } + @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties static class Config {