Commit c2c6f49c authored by Stephane Nicoll's avatar Stephane Nicoll

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
parent 5d05347e
...@@ -22,8 +22,8 @@ import org.junit.Test; ...@@ -22,8 +22,8 @@ import org.junit.Test;
import org.springframework.boot.actuate.env.EnvironmentEndpoint; import org.springframework.boot.actuate.env.EnvironmentEndpoint;
import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentDescriptor; import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentDescriptor;
import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentDescriptor.PropertySourceDescriptor; import org.springframework.boot.actuate.env.EnvironmentEndpoint.PropertySourceDescriptor;
import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentDescriptor.PropertySourceDescriptor.PropertyValueDescriptor; import org.springframework.boot.actuate.env.EnvironmentEndpoint.PropertyValueDescriptor;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ApplicationContextRunner;
......
...@@ -25,14 +25,15 @@ import java.util.function.Predicate; ...@@ -25,14 +25,15 @@ import java.util.function.Predicate;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Stream; import java.util.stream.Stream;
import com.fasterxml.jackson.annotation.JsonInclude;
import org.springframework.boot.actuate.endpoint.Sanitizer; import org.springframework.boot.actuate.endpoint.Sanitizer;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector; 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.PlaceholdersResolver;
import org.springframework.boot.context.properties.bind.PropertySourcesPlaceholdersResolver; 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.boot.origin.OriginLookup;
import org.springframework.core.env.CompositePropertySource; import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
...@@ -54,6 +55,7 @@ import org.springframework.web.bind.annotation.ResponseStatus; ...@@ -54,6 +55,7 @@ import org.springframework.web.bind.annotation.ResponseStatus;
* @author Phillip Webb * @author Phillip Webb
* @author Christian Dupuis * @author Christian Dupuis
* @author Madhura Bhave * @author Madhura Bhave
* @author Stephane Nicoll
* @since 2.0.0 * @since 2.0.0
*/ */
@Endpoint(id = "env") @Endpoint(id = "env")
...@@ -80,8 +82,8 @@ public class EnvironmentEndpoint { ...@@ -80,8 +82,8 @@ public class EnvironmentEndpoint {
} }
@ReadOperation @ReadOperation
public EnvironmentDescriptor environmentEntry(@Selector String toMatch) { public EnvironmentEntryDescriptor environmentEntry(@Selector String toMatch) {
return getEnvironmentDescriptor(toMatch::equals); return getEnvironmentEntryDescriptor(toMatch);
} }
private EnvironmentDescriptor getEnvironmentDescriptor( private EnvironmentDescriptor getEnvironmentDescriptor(
...@@ -99,6 +101,46 @@ public class EnvironmentEndpoint { ...@@ -99,6 +101,46 @@ public class EnvironmentEndpoint {
Arrays.asList(this.environment.getActiveProfiles()), propertySources); Arrays.asList(this.environment.getActiveProfiles()), propertySources);
} }
private EnvironmentEntryDescriptor getEnvironmentEntryDescriptor(
String propertyName) {
Map<String, PropertyValueDescriptor> descriptors = getPropertySourceDescriptors(
propertyName);
PropertySummaryDescriptor summary = getPropertySummaryDescriptor(descriptors);
return new EnvironmentEntryDescriptor(summary,
Arrays.asList(this.environment.getActiveProfiles()),
toPropertySourceDescriptors(descriptors));
}
private List<PropertySourceEntryDescriptor> toPropertySourceDescriptors(
Map<String, PropertyValueDescriptor> descriptors) {
List<PropertySourceEntryDescriptor> result = new ArrayList<>();
for (Map.Entry<String, PropertyValueDescriptor> entry : descriptors.entrySet()) {
result.add(new PropertySourceEntryDescriptor(entry.getKey(), entry.getValue()));
}
return result;
}
private PropertySummaryDescriptor getPropertySummaryDescriptor(
Map<String, PropertyValueDescriptor> descriptors) {
for (Map.Entry<String, PropertyValueDescriptor> entry : descriptors.entrySet()) {
if (entry.getValue() != null) {
return new PropertySummaryDescriptor(entry.getKey(),
entry.getValue().getValue());
}
}
return null;
}
private Map<String, PropertyValueDescriptor> getPropertySourceDescriptors(
String propertyName) {
Map<String, PropertyValueDescriptor> 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, private PropertySourceDescriptor describeSource(String sourceName,
EnumerablePropertySource<?> source, PlaceholdersResolver resolver, EnumerablePropertySource<?> source, PlaceholdersResolver resolver,
Predicate<String> namePredicate) { Predicate<String> namePredicate) {
...@@ -109,7 +151,7 @@ public class EnvironmentEndpoint { ...@@ -109,7 +151,7 @@ public class EnvironmentEndpoint {
} }
private PropertyValueDescriptor describeValueOf(String name, private PropertyValueDescriptor describeValueOf(String name,
EnumerablePropertySource<?> source, PlaceholdersResolver resolver) { PropertySource<?> source, PlaceholdersResolver resolver) {
Object resolved = resolver.resolvePlaceholders(source.getProperty(name)); Object resolved = resolver.resolvePlaceholders(source.getProperty(name));
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
String origin = (source instanceof OriginLookup) String origin = (source instanceof OriginLookup)
...@@ -125,7 +167,9 @@ public class EnvironmentEndpoint { ...@@ -125,7 +167,9 @@ public class EnvironmentEndpoint {
private Map<String, PropertySource<?>> getPropertySourcesAsMap() { private Map<String, PropertySource<?>> getPropertySourcesAsMap() {
Map<String, PropertySource<?>> map = new LinkedHashMap<>(); Map<String, PropertySource<?>> map = new LinkedHashMap<>();
for (PropertySource<?> source : getPropertySources()) { for (PropertySource<?> source : getPropertySources()) {
extract("", map, source); if (!ConfigurationPropertySources.isMainConfigurationPropertySource(source)) {
extract("", map, source);
}
} }
return map; return map;
} }
...@@ -208,54 +252,141 @@ public class EnvironmentEndpoint { ...@@ -208,54 +252,141 @@ public class EnvironmentEndpoint {
return this.propertySources; 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<String, PropertyValueDescriptor> properties; private final PropertySummaryDescriptor property;
private PropertySourceDescriptor(String name, private final List<String> activeProfiles;
Map<String, PropertyValueDescriptor> properties) {
this.name = name;
this.properties = properties;
}
public String getName() { private final List<PropertySourceEntryDescriptor> propertySources;
return this.name;
}
public Map<String, PropertyValueDescriptor> getProperties() { private EnvironmentEntryDescriptor(PropertySummaryDescriptor property,
return this.properties; List<String> activeProfiles,
} List<PropertySourceEntryDescriptor> propertySources) {
this.property = property;
this.activeProfiles = activeProfiles;
this.propertySources = propertySources;
}
/** public PropertySummaryDescriptor getProperty() {
* A description of a property's value, including its origin if available. return this.property;
*/ }
public static final class PropertyValueDescriptor {
private final Object value; public List<String> getActiveProfiles() {
return this.activeProfiles;
}
private final String origin; public List<PropertySourceEntryDescriptor> 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() { private final String source;
return this.origin;
}
} 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<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.
*/
@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;
}
} }
/** /**
......
...@@ -53,7 +53,9 @@ public class EnvironmentEndpointWebIntegrationTests { ...@@ -53,7 +53,9 @@ public class EnvironmentEndpointWebIntegrationTests {
@Test @Test
public void sub() throws Exception { public void sub() throws Exception {
client.get().uri("/application/env/foo").exchange().expectStatus().isOk() 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 @Test
...@@ -75,8 +77,10 @@ public class EnvironmentEndpointWebIntegrationTests { ...@@ -75,8 +77,10 @@ public class EnvironmentEndpointWebIntegrationTests {
context.getEnvironment().getPropertySources() context.getEnvironment().getPropertySources()
.addFirst(new MapPropertySource("unresolved-placeholder", map)); .addFirst(new MapPropertySource("unresolved-placeholder", map));
client.get().uri("/application/env/my.foo").exchange().expectStatus().isOk() client.get().uri("/application/env/my.foo").exchange().expectStatus().isOk()
.expectBody().jsonPath(forProperty("unresolved-placeholder", "my.foo")) .expectBody()
.isEqualTo("${my.bar}"); .jsonPath("property.value").isEqualTo("${my.bar}")
.jsonPath(forPropertyEntry(
"unresolved-placeholder")).isEqualTo("${my.bar}");
} }
@Test @Test
...@@ -87,8 +91,9 @@ public class EnvironmentEndpointWebIntegrationTests { ...@@ -87,8 +91,9 @@ public class EnvironmentEndpointWebIntegrationTests {
context.getEnvironment().getPropertySources() context.getEnvironment().getPropertySources()
.addFirst(new MapPropertySource("placeholder", map)); .addFirst(new MapPropertySource("placeholder", map));
client.get().uri("/application/env/my.foo").exchange().expectStatus().isOk() client.get().uri("/application/env/my.foo").exchange().expectStatus().isOk()
.expectBody().jsonPath(forProperty("placeholder", "my.foo")) .expectBody()
.isEqualTo("******"); .jsonPath("property.value").isEqualTo("******")
.jsonPath(forPropertyEntry("placeholder")).isEqualTo("******");
} }
@Test @Test
...@@ -123,6 +128,10 @@ public class EnvironmentEndpointWebIntegrationTests { ...@@ -123,6 +128,10 @@ public class EnvironmentEndpointWebIntegrationTests {
+ "'].value"; + "'].value";
} }
private String forPropertyEntry(String source) {
return "propertySources[?(@.name=='" + source + "')].property.value";
}
@Configuration @Configuration
static class TestConfiguration { static class TestConfiguration {
......
...@@ -45,6 +45,18 @@ public final class ConfigurationPropertySources { ...@@ -45,6 +45,18 @@ public final class ConfigurationPropertySources {
private 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 * Attach a {@link ConfigurationPropertySource} support to the specified
* {@link Environment}. Adapts each {@link PropertySource} managed by the environment * {@link Environment}. Adapts each {@link PropertySource} managed by the environment
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment