diff --git a/pom.xml b/pom.xml index 8906b1c8d2..18ccfe7242 100644 --- a/pom.xml +++ b/pom.xml @@ -150,6 +150,7 @@ spring-batch spring-bom spring-boot + spring-boot-2-actuator spring-boot-keycloak spring-boot-bootstrap spring-cloud-data-flow diff --git a/spring-boot-2-actuator/.gitignore b/spring-boot-2-actuator/.gitignore new file mode 100644 index 0000000000..60be5b80aa --- /dev/null +++ b/spring-boot-2-actuator/.gitignore @@ -0,0 +1,4 @@ +/target/ +.settings/ +.classpath +.project diff --git a/spring-boot-2-actuator/README.MD b/spring-boot-2-actuator/README.MD new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spring-boot-2-actuator/pom.xml b/spring-boot-2-actuator/pom.xml new file mode 100644 index 0000000000..a0ae6e59a2 --- /dev/null +++ b/spring-boot-2-actuator/pom.xml @@ -0,0 +1,166 @@ + + 4.0.0 + com.baeldung + spring-boot-actuator + 0.0.1-SNAPSHOT + jar + spring-boot + This is simple boot application for Spring boot actuator test + + + + org.springframework.boot + spring-boot-starter-parent + 2.0.0.M7 + + + + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-webflux + + + + org.springframework.boot + spring-boot-starter-test + test + + + io.projectreactor + reactor-test + test + + + org.springframework.security + spring-security-test + test + + + + + spring-boot-2-actuator + + + src/main/resources + true + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.7.0 + + 1.8 + 1.8 + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/*IntegrationTest.java + + + + + + + + + + + integration + + + + org.apache.maven.plugins + maven-surefire-plugin + + + integration-test + + test + + + + **/*IntegrationTest.java + + + + + + + json + + + + + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + org.baeldung.ActuatorApplication + UTF-8 + 1.8 + + + diff --git a/spring-boot-2-actuator/src/main/java/org/baeldung/ActuatorApplication.java b/spring-boot-2-actuator/src/main/java/org/baeldung/ActuatorApplication.java new file mode 100644 index 0000000000..39483c0260 --- /dev/null +++ b/spring-boot-2-actuator/src/main/java/org/baeldung/ActuatorApplication.java @@ -0,0 +1,12 @@ +package org.baeldung; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ActuatorApplication { + + public static void main(String[] args) { + SpringApplication.run(ActuatorApplication.class, args); + } +} diff --git a/spring-boot-2-actuator/src/main/java/org/baeldung/DownstreamServiceReactiveHealthIndicator.java b/spring-boot-2-actuator/src/main/java/org/baeldung/DownstreamServiceReactiveHealthIndicator.java new file mode 100644 index 0000000000..95cc43f976 --- /dev/null +++ b/spring-boot-2-actuator/src/main/java/org/baeldung/DownstreamServiceReactiveHealthIndicator.java @@ -0,0 +1,23 @@ +package org.baeldung; + +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.ReactiveHealthIndicator; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Mono; + +@Component +public class DownstreamServiceReactiveHealthIndicator implements ReactiveHealthIndicator { + + @Override + public Mono health() { + return checkDownstreamServiceHealth().onErrorResume( + ex -> Mono.just(new Health.Builder().down(ex).build()) + ); + } + + private Mono checkDownstreamServiceHealth() { + // we could use WebClient to check health reactively + return Mono.just(new Health.Builder().up().build()); + } + +} \ No newline at end of file diff --git a/spring-boot-2-actuator/src/main/java/org/baeldung/FeaturesEndpoint.java b/spring-boot-2-actuator/src/main/java/org/baeldung/FeaturesEndpoint.java new file mode 100644 index 0000000000..0b13bfe737 --- /dev/null +++ b/spring-boot-2-actuator/src/main/java/org/baeldung/FeaturesEndpoint.java @@ -0,0 +1,47 @@ +package org.baeldung; + +import org.springframework.boot.actuate.endpoint.annotation.*; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Component +@Endpoint(id = "features", enableByDefault = true) +public class FeaturesEndpoint { + + private Map features = new ConcurrentHashMap<>(); + + @ReadOperation + public Map features() { + return features; + } + + @ReadOperation + public Feature feature(@Selector String name) { + return features.get(name); + } + + @WriteOperation + public void configureFeature(@Selector String name, Feature feature) { + features.put(name, feature); + } + + @DeleteOperation + public void deleteFeature(@Selector String name) { + features.remove(name); + } + + public static class Feature { + private Boolean enabled; + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + } + +} diff --git a/spring-boot-2-actuator/src/main/java/org/baeldung/InfoWebEndpointExtension.java b/spring-boot-2-actuator/src/main/java/org/baeldung/InfoWebEndpointExtension.java new file mode 100644 index 0000000000..49d88047d4 --- /dev/null +++ b/spring-boot-2-actuator/src/main/java/org/baeldung/InfoWebEndpointExtension.java @@ -0,0 +1,32 @@ +package org.baeldung; + +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; +import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension; +import org.springframework.boot.actuate.info.InfoEndpoint; +import org.springframework.stereotype.Component; + +import java.util.Map; + +@Component +@EndpointWebExtension(endpoint = InfoEndpoint.class) +public class InfoWebEndpointExtension { + + private final InfoEndpoint delegate; + + public InfoWebEndpointExtension(InfoEndpoint infoEndpoint) { + this.delegate = infoEndpoint; + } + + @ReadOperation + public WebEndpointResponse info() { + Map info = this.delegate.info(); + Integer status = getStatus(info); + return new WebEndpointResponse<>(info, status); + } + + private Integer getStatus(Map info) { + // return 5xx if this is a snapshot + return 200; + } +} \ No newline at end of file diff --git a/spring-boot-2-actuator/src/main/java/org/baeldung/config/SecurityConfig.java b/spring-boot-2-actuator/src/main/java/org/baeldung/config/SecurityConfig.java new file mode 100644 index 0000000000..cc2485577e --- /dev/null +++ b/spring-boot-2-actuator/src/main/java/org/baeldung/config/SecurityConfig.java @@ -0,0 +1,32 @@ +package org.baeldung.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; +import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.web.server.SecurityWebFilterChain; + +@EnableWebFluxSecurity +public class SecurityConfig { + + @Bean + public SecurityWebFilterChain securityWebFilterChain( + ServerHttpSecurity http) { + return http.authorizeExchange() + // We'll open up all actuator endpoints for demo purposes + .pathMatchers("/actuator/**").permitAll() + .anyExchange().authenticated() + .and().build(); + } + + @Bean + public MapReactiveUserDetailsService userDetailsService() { + UserDetails user = User.withDefaultPasswordEncoder() + .username("user").password("password").roles("USER") + .build(); + return new MapReactiveUserDetailsService(user); + } + +} diff --git a/spring-boot-2-actuator/src/main/resources/application.properties b/spring-boot-2-actuator/src/main/resources/application.properties new file mode 100644 index 0000000000..16e1269a2a --- /dev/null +++ b/spring-boot-2-actuator/src/main/resources/application.properties @@ -0,0 +1,3 @@ +management.endpoints.web.expose=* + +info.app.name=Spring Boot 2 actuator Application \ No newline at end of file diff --git a/spring-boot-2-actuator/src/test/java/org/baeldung/config/ActuatorInfoIntegrationTest.java b/spring-boot-2-actuator/src/test/java/org/baeldung/config/ActuatorInfoIntegrationTest.java new file mode 100644 index 0000000000..ff80614d10 --- /dev/null +++ b/spring-boot-2-actuator/src/test/java/org/baeldung/config/ActuatorInfoIntegrationTest.java @@ -0,0 +1,36 @@ +package org.baeldung.config; + +import org.baeldung.ActuatorApplication; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; +import static org.junit.Assert.*; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = ActuatorApplication.class) +public class ActuatorInfoIntegrationTest { + + @Autowired + private TestRestTemplate restTemplate; + + @Test + public void whenGetInfo_thenReturns200() throws IOException { + final ResponseEntity responseEntity = this.restTemplate.getForEntity("/actuator/info", String.class); + assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); + } + + @Test + public void whenFeatures_thenReturns200() throws IOException { + final ResponseEntity responseEntity = this.restTemplate.getForEntity("/actuator/features", String.class); + assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); + } +} \ No newline at end of file