From 8bebe6dea9f02ace2d8f8357ec7d0a954488a6c4 Mon Sep 17 00:00:00 2001 From: Meang Akira Tanaka Date: Tue, 14 Jul 2015 19:42:34 +0200 Subject: [PATCH] Info endpoint See gh-3492 --- .../EndpointAutoConfiguration.java | 41 ++----- .../InfoProviderAutoConfiguration.java | 62 ++++++++++ .../boot/actuate/endpoint/InfoEndpoint.java | 50 +++++--- .../actuate/info/EnvironmentInfoProvider.java | 61 ++++++++++ .../boot/actuate/info/Info.java | 84 +++++++++++++ .../boot/actuate/info/InfoProvider.java | 17 +++ .../info/ScmGitPropertiesInfoProvider.java | 105 ++++++++++++++++ .../main/resources/META-INF/spring.factories | 1 + .../EndpointAutoConfigurationTests.java | 4 + .../actuate/endpoint/InfoEndpointTests.java | 43 ++++++- .../endpoint/mvc/InfoMvcEndpointTests.java | 115 ++++++++++++++++++ ...cEndpointWithoutAnyInfoProvidersTests.java | 75 ++++++++++++ .../info/EnvironmentInfoProviderTest.java | 64 ++++++++++ .../ScmGitPropertiesInfoProviderTest.java | 98 +++++++++++++++ .../sample/actuator/ExampleInfoProvider.java | 21 ++++ 15 files changed, 786 insertions(+), 55 deletions(-) create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/InfoProviderAutoConfiguration.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/EnvironmentInfoProvider.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/Info.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/InfoProvider.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/ScmGitPropertiesInfoProvider.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/InfoMvcEndpointTests.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/InfoMvcEndpointWithoutAnyInfoProvidersTests.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/EnvironmentInfoProviderTest.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/ScmGitPropertiesInfoProviderTest.java create mode 100644 spring-boot-samples/spring-boot-sample-actuator/src/main/java/sample/actuator/ExampleInfoProvider.java diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java index 52d5092de2..b37d47136e 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java @@ -20,7 +20,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -47,6 +46,7 @@ import org.springframework.boot.actuate.endpoint.TraceEndpoint; import org.springframework.boot.actuate.health.HealthAggregator; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.OrderedHealthAggregator; +import org.springframework.boot.actuate.info.InfoProvider; import org.springframework.boot.actuate.trace.InMemoryTraceRepository; import org.springframework.boot.actuate.trace.TraceRepository; import org.springframework.boot.autoconfigure.AutoConfigureAfter; @@ -57,17 +57,16 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; -import org.springframework.boot.autoconfigure.info.GitInfo; import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration; import org.springframework.boot.bind.PropertiesConfigurationFactory; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.AnnotationAwareOrderComparator; -import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.StandardEnvironment; import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping; +import liquibase.integration.spring.SpringLiquibase; + /** * {@link EnableAutoConfiguration Auto-configuration} for common management * {@link Endpoint}s. @@ -78,24 +77,23 @@ import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping; * @author Christian Dupuis * @author Stephane Nicoll * @author Eddú Meléndez + * @author Meang Akira Tanaka + * */ @Configuration @AutoConfigureAfter({ FlywayAutoConfiguration.class, LiquibaseAutoConfiguration.class }) @EnableConfigurationProperties(EndpointProperties.class) public class EndpointAutoConfiguration { - @Autowired - private InfoPropertiesConfiguration properties; - - @Autowired(required = false) - private GitInfo gitInfo; - @Autowired(required = false) private HealthAggregator healthAggregator = new OrderedHealthAggregator(); @Autowired(required = false) private Map healthIndicators = new HashMap(); + @Autowired(required = false) + private Map infoProviders = new HashMap(); + @Autowired(required = false) private Collection publicMetrics; @@ -123,12 +121,7 @@ public class EndpointAutoConfiguration { @Bean @ConditionalOnMissingBean public InfoEndpoint infoEndpoint() throws Exception { - LinkedHashMap info = new LinkedHashMap(); - info.putAll(this.properties.infoMap()); - if (this.gitInfo != null && this.gitInfo.getBranch() != null) { - info.put("git", this.gitInfo); - } - return new InfoEndpoint(info); + return new InfoEndpoint(this.infoProviders); } @Bean @@ -212,20 +205,4 @@ public class EndpointAutoConfiguration { } - @Configuration - protected static class InfoPropertiesConfiguration { - - @Autowired - private final ConfigurableEnvironment environment = new StandardEnvironment(); - - public Map infoMap() throws Exception { - PropertiesConfigurationFactory> factory = new PropertiesConfigurationFactory>( - new LinkedHashMap()); - factory.setTargetName("info"); - factory.setPropertySources(this.environment.getPropertySources()); - return factory.getObject(); - } - - } - } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/InfoProviderAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/InfoProviderAutoConfiguration.java new file mode 100644 index 0000000000..aefba43f06 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/InfoProviderAutoConfiguration.java @@ -0,0 +1,62 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.actuate.info.EnvironmentInfoProvider; +import org.springframework.boot.actuate.info.InfoProvider; +import org.springframework.boot.actuate.info.ScmGitPropertiesInfoProvider; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.StandardEnvironment; +import org.springframework.core.io.Resource; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for standard {@link InfoProvider}s. + * + * @author Meang Akira Tanaka + * @since 1.3.0 + */ +@Configuration +@AutoConfigureBefore({ EndpointAutoConfiguration.class }) +public class InfoProviderAutoConfiguration { + + @Autowired + private final ConfigurableEnvironment environment = new StandardEnvironment(); + + @Value("${spring.git.properties:classpath:git.properties}") + private Resource gitProperties; + + @Bean + @ConditionalOnMissingBean(name = "environmentInfoProvider") + public InfoProvider environmentInfoProvider() throws Exception { + return new EnvironmentInfoProvider(environment); + } + + @Bean + @ConditionalOnMissingBean(name = "scmInfoProvider") + public InfoProvider scmInfoProvider() throws Exception { + return new ScmGitPropertiesInfoProvider(gitProperties); + } + + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/InfoEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/InfoEndpoint.java index af8396901c..9632b23ae1 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/InfoEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/InfoEndpoint.java @@ -16,43 +16,57 @@ package org.springframework.boot.actuate.endpoint; -import java.util.Collections; -import java.util.LinkedHashMap; import java.util.Map; +import org.springframework.boot.actuate.info.Info; +import org.springframework.boot.actuate.info.InfoProvider; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.util.Assert; /** * {@link Endpoint} to expose arbitrary application information. + * + * The information, which the {@link InfoEndpoint} can provide can be customized to display any informations, + * however initially the info endpoint will provide git version information (if available) and environment information, + * whose entries are prefixed with info. + * + * In order to add additional information to the endpoint, one has to implement a class, which implements the {@link org.springframework.boot.actuate.info.InfoProvider} + * interface and register it in the application context. The InfoEndpoint will automatically pick it up, when it is being instantiated. + * + * The standard InfoProvider for GIT is registered as the scmInfoProvider, and the registration can be changed + * in case standard provider does not meet ones requirements. + * + * @see org.springframework.boot.actuate.info.ScmGitPropertiesInfoProvider + * @see org.springframework.boot.actuate.info.EnvironmentInfoProvider * * @author Dave Syer + * @author Meang Akira Tanaka */ -@ConfigurationProperties(prefix = "endpoints.info") -public class InfoEndpoint extends AbstractEndpoint> { +@ConfigurationProperties(prefix = "endpoints.info", ignoreUnknownFields = false) +public class InfoEndpoint extends AbstractEndpoint { - private final Map info; + private final Map infoProviders; /** * Create a new {@link InfoEndpoint} instance. * - * @param info the info to expose + * @param infoProviders the infoProviders to be used */ - public InfoEndpoint(Map info) { + public InfoEndpoint(Map infoProviders) { super("info", false); - Assert.notNull(info, "Info must not be null"); - this.info = info; + Assert.notNull(infoProviders, "Info providers must not be null"); + this.infoProviders = infoProviders; } @Override - public Map invoke() { - Map info = new LinkedHashMap(this.info); - info.putAll(getAdditionalInfo()); - return info; + public Info invoke() { + Info result = new Info(); + for (InfoProvider provider : infoProviders.values()) { + Info info = provider.provide(); + if(info != null) { + result.put(provider.name(), info); + } + } + return result; } - - protected Map getAdditionalInfo() { - return Collections.emptyMap(); - } - } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/EnvironmentInfoProvider.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/EnvironmentInfoProvider.java new file mode 100644 index 0000000000..573580e7ca --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/EnvironmentInfoProvider.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.boot.actuate.info; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.boot.bind.PropertiesConfigurationFactory; +import org.springframework.core.env.ConfigurableEnvironment; + +/** + * A {@link InfoProvider} that provides all environment entries prefixed with info + * + * See something + * + * @author Meang Akira Tanaka + * @since 1.3.0 + */ +public class EnvironmentInfoProvider implements InfoProvider { + + private final ConfigurableEnvironment environment; + private final Map infoMap; + private final Info info; + + public EnvironmentInfoProvider(ConfigurableEnvironment environment) throws Exception { + this.environment = environment; + infoMap = extractInfoFromEnvironment(); + this.info = new Info(infoMap); + } + + @Override + public String name() { + return "environment"; + } + + @Override + public Info provide() { + return info; + } + + private Map extractInfoFromEnvironment() throws Exception { + PropertiesConfigurationFactory> factory = new PropertiesConfigurationFactory>( + new LinkedHashMap()); + factory.setTargetName("info"); + factory.setPropertySources(this.environment.getPropertySources()); + return factory.getObject(); + } +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/Info.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/Info.java new file mode 100644 index 0000000000..1b339b8766 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/Info.java @@ -0,0 +1,84 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.info; + +import java.util.HashMap; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +/** + * Carries information from a specific info provider + * + * @see org.springframework.boot.actuate.endpoint.InfoEndpoint + * + * @author Meang Akira Tanaka + * @since 1.3.0 + */ +@JsonInclude(Include.NON_EMPTY) +public final class Info { + + private final Map details = new HashMap(); + + public Info() { + } + + public Info(Map details) { + this.details.putAll(details); + } + + /** + * @return the details of the info or an empty map. + */ + @JsonAnyGetter + public Map getDetails() { + return this.details; + } + + public void put(String infoId, Object value) { + this.details.put(infoId, value); + } + + @SuppressWarnings("unchecked") + public T get(String infoId) { + return (T) this.details.get(infoId); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj != null && obj instanceof Info) { + Info other = (Info) obj; + return this.details.equals(other.details); + } + return false; + } + + @Override + public int hashCode() { + return this.details.hashCode(); + } + + @Override + public String toString() { + return getDetails().toString(); + } +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/InfoProvider.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/InfoProvider.java new file mode 100644 index 0000000000..ad51181d8c --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/InfoProvider.java @@ -0,0 +1,17 @@ +package org.springframework.boot.actuate.info; + +/** + * information provider for the info endpoint + * + * @author Meang Akira Tanaka + */ +public interface InfoProvider { + + String name(); + + /** + * @return a collection of information + */ + Info provide(); + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/ScmGitPropertiesInfoProvider.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/ScmGitPropertiesInfoProvider.java new file mode 100644 index 0000000000..7e6984cef0 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/ScmGitPropertiesInfoProvider.java @@ -0,0 +1,105 @@ +package org.springframework.boot.actuate.info; + +import java.util.Properties; + +import org.springframework.boot.bind.PropertiesConfigurationFactory; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PropertiesLoaderUtils; + +/** + * A {@link InfoProvider} that provides git information extracted from the git.properties file generated by the maven plugin + * pl.project13.maven:git-commit-id-plugin. + * + * @author Meang Akira Tanaka + * @since 1.3.0 + */ +public class ScmGitPropertiesInfoProvider implements InfoProvider { + + private final Resource gitPropertiesResource; + private final GitInfo gitInfo; + + public ScmGitPropertiesInfoProvider(Resource gitPropertiesResource) throws Exception { + this.gitPropertiesResource = gitPropertiesResource; + gitInfo = extractGitInfo(); + } + + @Override + public String name() { + return "git"; + } + + @Override + public Info provide() { + if(gitInfo == null) { + return null; + } + + Info result = new Info(); + + result.put("branch", gitInfo.getBranch()); + result.put("commit", gitInfo.getCommit()); + + return result; + } + + + private GitInfo extractGitInfo() throws Exception { + PropertiesConfigurationFactory factory = new PropertiesConfigurationFactory( + new GitInfo()); + factory.setTargetName("git"); + Properties properties = new Properties(); + if (this.gitPropertiesResource.exists()) { + properties = PropertiesLoaderUtils.loadProperties(this.gitPropertiesResource); + } else { + return null; + } + factory.setProperties(properties); + return factory.getObject(); + } + + + public static class GitInfo { + + private String branch; + + private final Commit commit = new Commit(); + + public String getBranch() { + return this.branch; + } + + public void setBranch(String branch) { + this.branch = branch; + } + + public Commit getCommit() { + return this.commit; + } + + public static class Commit { + + private String id; + + private String time; + + public String getId() { + return this.id == null ? "" + : (this.id.length() > 7 ? this.id.substring(0, 7) : this.id); + } + + public void setId(String id) { + this.id = id; + } + + public String getTime() { + return this.time; + } + + public void setTime(String time) { + this.time = time; + } + + } + + } +} diff --git a/spring-boot-actuator/src/main/resources/META-INF/spring.factories b/spring-boot-actuator/src/main/resources/META-INF/spring.factories index 3874247ead..40a1efafd0 100644 --- a/spring-boot-actuator/src/main/resources/META-INF/spring.factories +++ b/spring-boot-actuator/src/main/resources/META-INF/spring.factories @@ -6,6 +6,7 @@ org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.EndpointMBeanExportAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.HealthIndicatorAutoConfiguration,\ +org.springframework.boot.actuate.autoconfigure.InfoProviderAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.JolokiaAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.ManagementWebSecurityAutoConfiguration,\ diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfigurationTests.java index 0b89c82090..4a86b96c53 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfigurationTests.java @@ -37,6 +37,7 @@ import org.springframework.boot.actuate.endpoint.RequestMappingEndpoint; import org.springframework.boot.actuate.endpoint.ShutdownEndpoint; import org.springframework.boot.actuate.endpoint.TraceEndpoint; import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.info.Info; import org.springframework.boot.actuate.metrics.Metric; import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; @@ -59,6 +60,8 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Christian Dupuis * @author Stephane Nicoll * @author Eddú Meléndez + * @author Meang Akira Tanaka + * */ public class EndpointAutoConfigurationTests { @@ -143,6 +146,7 @@ public class EndpointAutoConfigurationTests { EnvironmentTestUtils.addEnvironment(this.context, "info.foo:bar"); this.context.register(ProjectInfoAutoConfiguration.class, EndpointAutoConfiguration.class); this.context.refresh(); + InfoEndpoint endpoint = this.context.getBean(InfoEndpoint.class); assertThat(endpoint).isNotNull(); assertThat(endpoint.invoke().get("git")).isNotNull(); diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/InfoEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/InfoEndpointTests.java index d0a0573a32..7715fd691a 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/InfoEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/InfoEndpointTests.java @@ -16,10 +16,14 @@ package org.springframework.boot.actuate.endpoint; -import java.util.Collections; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +import java.util.Map; import org.junit.Test; - +import org.springframework.boot.actuate.info.Info; +import org.springframework.boot.actuate.info.InfoProvider; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -31,6 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Phillip Webb * @author Dave Syer + * @author Meang Akira Tanaka */ public class InfoEndpointTests extends AbstractEndpointTests { @@ -40,7 +45,15 @@ public class InfoEndpointTests extends AbstractEndpointTests { @Test public void invoke() throws Exception { - assertThat(getEndpointBean().invoke().get("a")).isEqualTo("b"); + Info actual = ((Info) getEndpointBean().invoke().get("environment")); + assertThat(actual.get("key1"), equalTo((Object) "value1")); + } + + @Test + public void invoke_HasProvider_GetProviderInfo() throws Exception { + @SuppressWarnings("unchecked") + Map actual = ((Map) getEndpointBean().invoke().get("infoProvider")); + assertThat(actual.get("key1"), equalTo((Object) "value1")); } @Configuration @@ -48,9 +61,29 @@ public class InfoEndpointTests extends AbstractEndpointTests { public static class Config { @Bean - public InfoEndpoint endpoint() { - return new InfoEndpoint(Collections.singletonMap("a", "b")); + public InfoProvider infoProvider() { + return new InfoProvider() { + + @Override + public String name() { + return "environment"; + } + + @Override + public Info provide() { + Info result = new Info(); + result.put("key1", "value1"); + + return result; + } + + }; } + + @Bean + public InfoEndpoint endpoint(Map infoProviders) { + return new InfoEndpoint(infoProviders); + } } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/InfoMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/InfoMvcEndpointTests.java new file mode 100644 index 0000000000..20644b861e --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/InfoMvcEndpointTests.java @@ -0,0 +1,115 @@ +package org.springframework.boot.actuate.endpoint.mvc; + +import static org.hamcrest.Matchers.containsString; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.Map; + +import org.elasticsearch.common.collect.Maps; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration; +import org.springframework.boot.actuate.endpoint.InfoEndpoint; +import org.springframework.boot.actuate.endpoint.mvc.InfoMvcEndpointTests.TestConfiguration; +import org.springframework.boot.actuate.info.Info; +import org.springframework.boot.actuate.info.InfoProvider; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +/** + * Tests for {@link InfoMvcEndpointTests} + * + * @author Meang Akira Tanaka + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = { TestConfiguration.class }) +@WebAppConfiguration +@TestPropertySource(properties = {"info.app.name=MyService"}) +public class InfoMvcEndpointTests { + @Autowired + private WebApplicationContext context; + + private MockMvc mvc; + + @Before + public void setUp() { + + this.context.getBean(InfoEndpoint.class).setEnabled(true); + this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); + } + + @Test + public void home() throws Exception { + this.mvc.perform(get("/info")).andExpect(status().isOk()) + .andExpect(content().string(containsString("\"beanName2\":{\"key22\":\"value22\",\"key21\":\"value21\"},\"beanName1\":{\"key12\":\"value12\",\"key11\":\"value11\"}"))); + } + + @Import({ JacksonAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, + EndpointWebMvcAutoConfiguration.class, + WebMvcAutoConfiguration.class, + ManagementServerPropertiesAutoConfiguration.class }) + @Configuration + public static class TestConfiguration { + + private Map infoProviders = Maps.newHashMap(); + + public TestConfiguration() { + InfoProvider infoProvider1 = new InfoProvider() { + + @Override + public Info provide() { + Info result = new Info(); + result.put("key11", "value11"); + result.put("key12", "value12"); + return result; + } + + @Override + public String name() { + return "beanName1"; + } + }; + infoProviders.put("beanName1", infoProvider1); + + InfoProvider infoProvider2 = new InfoProvider() { + + @Override + public Info provide() { + Info result = new Info(); + result.put("key21", "value21"); + result.put("key22", "value22"); + return result; + } + + @Override + public String name() { + return "beanName2"; + } + }; + infoProviders.put("beanName2", infoProvider2); + } + + @Bean + public InfoEndpoint endpoint() { + return new InfoEndpoint(infoProviders); + } + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/InfoMvcEndpointWithoutAnyInfoProvidersTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/InfoMvcEndpointWithoutAnyInfoProvidersTests.java new file mode 100644 index 0000000000..99d07fa5e5 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/InfoMvcEndpointWithoutAnyInfoProvidersTests.java @@ -0,0 +1,75 @@ +package org.springframework.boot.actuate.endpoint.mvc; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.HashMap; +import java.util.Map; + +import org.elasticsearch.common.collect.Maps; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration; +import org.springframework.boot.actuate.endpoint.InfoEndpoint; +import org.springframework.boot.actuate.endpoint.mvc.InfoMvcEndpointWithoutAnyInfoProvidersTests.TestConfiguration; +import org.springframework.boot.actuate.info.InfoProvider; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +/** + * Tests for {@link InfoMvcEndpointWithoutAnyInfoProvidersTests} + * + * @author Meang Akira Tanaka + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = { TestConfiguration.class }) +@WebAppConfiguration +public class InfoMvcEndpointWithoutAnyInfoProvidersTests { + @Autowired + private WebApplicationContext context; + + private MockMvc mvc; + + @Before + public void setUp() { + + this.context.getBean(InfoEndpoint.class).setEnabled(true); + this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); + } + + @Test + public void home() throws Exception { + this.mvc.perform(get("/info")).andExpect(status().isOk()); + } + + @Import({ JacksonAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, + EndpointWebMvcAutoConfiguration.class, + WebMvcAutoConfiguration.class, + ManagementServerPropertiesAutoConfiguration.class }) + @Configuration + public static class TestConfiguration { + + private Map infoProviders = Maps.newHashMap(); + + @Bean + public InfoEndpoint endpoint() { + return new InfoEndpoint(infoProviders); + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/EnvironmentInfoProviderTest.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/EnvironmentInfoProviderTest.java new file mode 100644 index 0000000000..2e0c2c65bd --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/EnvironmentInfoProviderTest.java @@ -0,0 +1,64 @@ +package org.springframework.boot.actuate.info; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; + +import java.util.Properties; + +import org.junit.Test; +import org.springframework.core.env.PropertiesPropertySource; +import org.springframework.core.env.PropertySource; +import org.springframework.core.env.StandardEnvironment; + +public class EnvironmentInfoProviderTest { + + @Test + public void provide_HasTwoRelevantEntries_ShowOnlyRelevantEntries() throws Exception { + String expectedAppName = "my app name"; + String expectedLanguage = "da-DK"; + + Properties properties = new Properties(); + properties.setProperty("info.app", expectedAppName); + properties.setProperty("info.lang", expectedLanguage); + properties.setProperty("logging.path", "notExpected"); + + PropertySource propertySource = new PropertiesPropertySource("mysettings", properties); + + StandardEnvironment environment = new StandardEnvironment(); + environment.getPropertySources().addLast(propertySource); + + EnvironmentInfoProvider environmentInfoProvider = new EnvironmentInfoProvider(environment); + + Info actual = environmentInfoProvider.provide(); + assertThat(actual.getDetails().size(), is(equalTo(2))); + assertThat((String) actual.get("app"), is(equalTo(expectedAppName))); + assertThat((String) actual.get("lang"), is(equalTo(expectedLanguage))); + } + + @Test + public void provide_HasNoRelevantEntries_NoEntries() throws Exception { + Properties properties = new Properties(); + properties.setProperty("logging.path", "notExpected"); + + PropertySource propertySource = new PropertiesPropertySource("mysettings", properties); + + StandardEnvironment environment = new StandardEnvironment(); + environment.getPropertySources().addLast(propertySource); + + EnvironmentInfoProvider environmentInfoProvider = new EnvironmentInfoProvider(environment); + + Info actual = environmentInfoProvider.provide(); + assertThat(actual.getDetails().size(), is(equalTo(0))); + } + + + @Test + public void provide_HasNoEntries_NoEntries() throws Exception { + EnvironmentInfoProvider environmentInfoProvider = new EnvironmentInfoProvider(new StandardEnvironment()); + + Info actual = environmentInfoProvider.provide(); + assertThat(actual.getDetails().size(), is(equalTo(0))); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/ScmGitPropertiesInfoProviderTest.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/ScmGitPropertiesInfoProviderTest.java new file mode 100644 index 0000000000..90eaf0fd55 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/ScmGitPropertiesInfoProviderTest.java @@ -0,0 +1,98 @@ +package org.springframework.boot.actuate.info; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.junit.Test; +import org.springframework.boot.actuate.info.ScmGitPropertiesInfoProvider.GitInfo.Commit; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; + +public class ScmGitPropertiesInfoProviderTest { + + + @Test + public void provide_HasBadFormatButExists_EmptyInfoReturned() throws Exception { + Resource resource = new ByteArrayResource("GARBAGE".getBytes()); + ScmGitPropertiesInfoProvider scmGitPropertiesInfoProvider = new ScmGitPropertiesInfoProvider(resource); + + Info actual = scmGitPropertiesInfoProvider.provide(); + assertThat(actual, is(not(nullValue()))); + assertThat((String) actual.get("branch"), is(nullValue())); + Commit actualCommit = (Commit) actual.get("commit"); + assertThat(actualCommit, is(not(nullValue()))); + assertThat(actualCommit.getId(), is(equalTo(""))); + assertThat(actualCommit.getTime(), is(nullValue())); + } + + @Test + public void provide_HasValidFormat_ExpectedDataReturned() throws Exception { + String gitProperties = "git.commit.id.abbrev=e02a4f3\r\n" + + "git.commit.user.email=dsyer@vmware.com\r\n" + + "git.commit.message.full=Update Spring\r\n" + + "git.commit.id=e02a4f3b6f452cdbf6dd311f1362679eb4c31ced\r\n" + + "git.commit.message.short=Update Spring\r\n" + + "git.commit.user.name=Dave Syer\r\n" + + "git.build.user.name=Dave Syer\r\n" + + "git.build.user.email=dsyer@vmware.com\r\n" + + "git.branch=develop\r\n" + + "git.commit.time=2013-04-24T08\\:42\\:13+0100\r\n" + + "git.build.time=2013-05-23T09\\:26\\:42+0100\r\n"; + + Resource resource = new ByteArrayResource(gitProperties.getBytes()); + ScmGitPropertiesInfoProvider scmGitPropertiesInfoProvider = new ScmGitPropertiesInfoProvider(resource); + + Info actual = scmGitPropertiesInfoProvider.provide(); + assertThat(actual, is(not(nullValue()))); + assertThat((String) actual.get("branch"), is(equalTo("develop"))); + Commit actualCommit = (Commit) actual.get("commit"); + assertThat(actualCommit, is(not(nullValue()))); + assertThat(actualCommit.getId(), is(equalTo("e02a4f3"))); + assertThat(actualCommit.getTime(), is(equalTo("2013-04-24T08:42:13+0100"))); + } + + + @Test + public void provide_HasValidFormatButMissingCommitTime_ExpectedDataReturnedWithoutCommitTime() throws Exception { + String gitProperties = "git.commit.id.abbrev=e02a4f3\r\n" + + "git.commit.user.email=dsyer@vmware.com\r\n" + + "git.commit.message.full=Update Spring\r\n" + + "git.commit.id=e02a4f3b6f452cdbf6dd311f1362679eb4c31ced\r\n" + + "git.commit.message.short=Update Spring\r\n" + + "git.commit.user.name=Dave Syer\r\n" + + "git.build.user.name=Dave Syer\r\n" + + "git.build.user.email=dsyer@vmware.com\r\n" + + "git.branch=develop\r\n" + + "git.build.time=2013-05-23T09\\:26\\:42+0100\r\n"; + + Resource resource = new ByteArrayResource(gitProperties.getBytes()); + ScmGitPropertiesInfoProvider scmGitPropertiesInfoProvider = new ScmGitPropertiesInfoProvider(resource); + + Info actual = scmGitPropertiesInfoProvider.provide(); + assertThat(actual, is(not(nullValue()))); + assertThat((String) actual.get("branch"), is(equalTo("develop"))); + Commit actualCommit = (Commit) actual.get("commit"); + assertThat(actualCommit, is(not(nullValue()))); + assertThat(actualCommit.getId(), is(equalTo("e02a4f3"))); + assertThat(actualCommit.getTime(), is(nullValue())); + } + + + @Test + public void provide_DoesNotExists_NullReturned() throws Exception { + + Resource resource = mock(Resource.class); + when(resource.exists()) + .thenReturn(false); + ScmGitPropertiesInfoProvider scmGitPropertiesInfoProvider = new ScmGitPropertiesInfoProvider(resource); + + Info actual = scmGitPropertiesInfoProvider.provide(); + assertThat(actual, is(nullValue())); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-actuator/src/main/java/sample/actuator/ExampleInfoProvider.java b/spring-boot-samples/spring-boot-sample-actuator/src/main/java/sample/actuator/ExampleInfoProvider.java new file mode 100644 index 0000000000..822d5b8d33 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-actuator/src/main/java/sample/actuator/ExampleInfoProvider.java @@ -0,0 +1,21 @@ +package sample.actuator; + +import org.springframework.boot.actuate.info.Info; +import org.springframework.boot.actuate.info.InfoProvider; +import org.springframework.stereotype.Component; + +@Component +public class ExampleInfoProvider implements InfoProvider { + + @Override + public Info provide() { + Info result = new Info(); + result.put("somekey", "somevalue"); + return result; + } + + @Override + public String name() { + return "example"; + } +}