From 3d2eead1df3c9a231a0b6268f2d209d4570ca522 Mon Sep 17 00:00:00 2001 From: Rohan Kumar Date: Thu, 29 Aug 2024 15:52:00 +0530 Subject: [PATCH] feat(spring-boot): added support WebFlux SpringBoot projects when generating liveness/readiness probes SpringBootHealthCheckEnricher now considers WebFlux dependency while adding liveness and readiness probes. If user is depending on webflux dependency and is using `spring.webflux.base-path` property, it would automatically be picked up in probe endpoints Signed-off-by: Rohan Kumar --- CHANGELOG.md | 1 + .../common/util/SpringBootConfiguration.java | 4 +- .../jkube/kit/common/util/SpringBootUtil.java | 5 ++ .../kit/common/util/SpringBootUtilTest.java | 23 ++++++ .../_jkube_healthcheck_spring_boot.adoc | 2 + .../SpringBootHealthCheckEnricher.java | 11 ++- ...ingBootHealthCheckEnricherTestSupport.java | 76 +++++++++++++++++++ 7 files changed, 119 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cca74ea0a..cd85d6e5aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Usage: ./scripts/extract-changelog-for-version.sh 1.3.37 5 ``` ### 1.18-SNAPSHOT +* Fix #1125: Support WebFlux SpringBoot projects when it comes to generate probes for actuators ### 1.17.0 (2024-08-13) * Fix #494: Support for Micronaut Framework Native Images diff --git a/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/SpringBootConfiguration.java b/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/SpringBootConfiguration.java index 7349afe7d7..c396356c34 100644 --- a/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/SpringBootConfiguration.java +++ b/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/SpringBootConfiguration.java @@ -35,6 +35,7 @@ public class SpringBootConfiguration { private String managementContextPath; private String actuatorBasePath; private String actuatorDefaultBasePath; + private String webFluxBasePath; private boolean managementHealthProbesEnabled; public static SpringBootConfiguration from(JavaProject project) { @@ -63,7 +64,8 @@ public static SpringBootConfiguration from(JavaProject project) { .serverContextPath(properties.getProperty("server.context-path")) .managementContextPath(properties.getProperty("management.context-path")) .actuatorBasePath("") - .actuatorDefaultBasePath(""); + .actuatorDefaultBasePath("") + .webFluxBasePath(properties.getProperty("spring.webflux.base-path")); if (majorVersion > 1) { configBuilder .managementPort(Optional.ofNullable(properties.getProperty("management.server.port")).map(Integer::parseInt).orElse(null)) diff --git a/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/SpringBootUtil.java b/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/SpringBootUtil.java index 018c03b5c9..186fc4c51e 100644 --- a/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/SpringBootUtil.java +++ b/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/SpringBootUtil.java @@ -39,6 +39,7 @@ public class SpringBootUtil { public static final String DEV_TOOLS_REMOTE_SECRET = "spring.devtools.remote.secret"; public static final String DEV_TOOLS_REMOTE_SECRET_ENV = "SPRING_DEVTOOLS_REMOTE_SECRET"; + private static final String SPRING_WEB_FLUX_ARTIFACT_ID = "spring-boot-starter-webflux"; private static final String PLACEHOLDER_PREFIX = "${"; private static final String PLACEHOLDER_SUFFIX = "}"; private static final String VALUE_SEPARATOR = ":"; @@ -149,5 +150,9 @@ public static File findNativeArtifactFile(JavaProject project) { } return null; } + + public static boolean hasSpringWebFluxDependency(JavaProject javaProject) { + return JKubeProjectUtil.hasDependency(javaProject, SPRING_BOOT_GROUP_ID, SPRING_WEB_FLUX_ARTIFACT_ID); + } } diff --git a/jkube-kit/common/src/test/java/org/eclipse/jkube/kit/common/util/SpringBootUtilTest.java b/jkube-kit/common/src/test/java/org/eclipse/jkube/kit/common/util/SpringBootUtilTest.java index 76162b7ada..44ef5a8090 100644 --- a/jkube-kit/common/src/test/java/org/eclipse/jkube/kit/common/util/SpringBootUtilTest.java +++ b/jkube-kit/common/src/test/java/org/eclipse/jkube/kit/common/util/SpringBootUtilTest.java @@ -389,6 +389,29 @@ void findNativeArtifactFile_whenNativeExecutableInStandardGradleNativeDirectory_ assertThat(nativeArtifactFound).hasName("sample"); } + @Test + void hasSpringWebFluxDependency_whenWebFluxDependencyPresent_thenReturnTrue() { + // Given + JavaProject javaProject = JavaProject.builder().dependency(Dependency.builder() + .groupId("org.springframework.boot") + .artifactId("spring-boot-starter-webflux") + .build()) + .build(); + // When + Then + assertThat(SpringBootUtil.hasSpringWebFluxDependency(javaProject)).isTrue(); + } + + @Test + void hasSpringWebFluxDependency_whenNoWebFluxDependencyPresent_thenReturnFalse() { + // Given + JavaProject javaProject = JavaProject.builder().dependency(Dependency.builder() + .groupId("org.springframework.boot") + .artifactId("spring-boot-starter-web") + .build()).build(); + // When + Then + assertThat(SpringBootUtil.hasSpringWebFluxDependency(javaProject)).isFalse(); + } + static URLClassLoader createClassLoader(File temporaryFolder, String resource) throws IOException { File applicationProp = new File(Objects.requireNonNull(SpringBootUtilTest.class.getResource(resource)).getPath()); File classesInTarget = new File(new File(temporaryFolder, "target"), "classes"); diff --git a/jkube-kit/doc/src/main/asciidoc/inc/enricher/spring-boot-healthcheck/_jkube_healthcheck_spring_boot.adoc b/jkube-kit/doc/src/main/asciidoc/inc/enricher/spring-boot-healthcheck/_jkube_healthcheck_spring_boot.adoc index 03ed9ad9cb..93d979ba20 100644 --- a/jkube-kit/doc/src/main/asciidoc/inc/enricher/spring-boot-healthcheck/_jkube_healthcheck_spring_boot.adoc +++ b/jkube-kit/doc/src/main/asciidoc/inc/enricher/spring-boot-healthcheck/_jkube_healthcheck_spring_boot.adoc @@ -12,6 +12,8 @@ ifeval::["{plugin-type}" == "gradle"] include::gradle/_actuator_dependency.adoc[] endif::[] +If you're using Spring Boot WebFlux, this enricher would automatically read `spring.webflux.base-path` property to infer base path for health check endpoints. + The enricher will try to discover the settings from the `application.properties` / `application.yaml` Spring Boot configuration file. `/actuator/health` is the default endpoint for the liveness and readiness probes. diff --git a/jkube-kit/jkube-kit-spring-boot/src/main/java/org/eclipse/jkube/springboot/enricher/SpringBootHealthCheckEnricher.java b/jkube-kit/jkube-kit-spring-boot/src/main/java/org/eclipse/jkube/springboot/enricher/SpringBootHealthCheckEnricher.java index d9bc10375b..09f0f81dd3 100644 --- a/jkube-kit/jkube-kit-spring-boot/src/main/java/org/eclipse/jkube/springboot/enricher/SpringBootHealthCheckEnricher.java +++ b/jkube-kit/jkube-kit-spring-boot/src/main/java/org/eclipse/jkube/springboot/enricher/SpringBootHealthCheckEnricher.java @@ -24,6 +24,8 @@ import org.eclipse.jkube.kit.enricher.specific.AbstractHealthCheckEnricher; import org.apache.commons.lang3.StringUtils; +import static org.eclipse.jkube.kit.common.util.SpringBootUtil.hasSpringWebFluxDependency; + /** * Enriches spring-boot containers with health checks if the actuator module is present. */ @@ -112,8 +114,13 @@ protected Probe buildProbe(Integer initialDelay, Integer period, Integer timeout springBootConfiguration.getManagementContextPath() : ""; } else { scheme = StringUtils.isNotBlank(springBootConfiguration.getServerKeystore()) ? SCHEME_HTTPS : SCHEME_HTTP; - prefix = StringUtils.isNotBlank(springBootConfiguration.getServerContextPath()) ? - springBootConfiguration.getServerContextPath() : ""; + if (hasSpringWebFluxDependency(getContext().getProject()) && StringUtils.isNotBlank(springBootConfiguration.getWebFluxBasePath())) { + prefix = springBootConfiguration.getWebFluxBasePath(); + } else if (StringUtils.isNotBlank(springBootConfiguration.getServerContextPath())) { + prefix = springBootConfiguration.getServerContextPath(); + } else { + prefix = ""; + } prefix += StringUtils.isNotBlank(springBootConfiguration.getServletPath()) ? springBootConfiguration.getServletPath() : ""; prefix += StringUtils.isNotBlank(springBootConfiguration.getManagementContextPath()) ? diff --git a/jkube-kit/jkube-kit-spring-boot/src/test/java/org/eclipse/jkube/springboot/enricher/AbstractSpringBootHealthCheckEnricherTestSupport.java b/jkube-kit/jkube-kit-spring-boot/src/test/java/org/eclipse/jkube/springboot/enricher/AbstractSpringBootHealthCheckEnricherTestSupport.java index 8752bf97a6..612c74d037 100644 --- a/jkube-kit/jkube-kit-spring-boot/src/test/java/org/eclipse/jkube/springboot/enricher/AbstractSpringBootHealthCheckEnricherTestSupport.java +++ b/jkube-kit/jkube-kit-spring-boot/src/test/java/org/eclipse/jkube/springboot/enricher/AbstractSpringBootHealthCheckEnricherTestSupport.java @@ -30,7 +30,10 @@ import org.eclipse.jkube.kit.config.resource.ProcessorConfig; import org.eclipse.jkube.kit.enricher.api.JKubeEnricherContext; import org.eclipse.jkube.kit.common.util.ProjectClassLoaders; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -701,6 +704,79 @@ void testLivenessAndReadinessProbesForDefaultPath_whenManagementHealthProbesEnab assertHTTPGetPathAndPort(readinessProbe,getActuatorDefaultBasePath() + "/health",8080); } + @Nested + @DisplayName("Spring Web Flux Dependency present") + class SpringWebFlux { + @BeforeEach + void setup() { + context.getProject().setDependencies(Collections.singletonList(Dependency.builder() + .groupId("org.springframework.boot") + .artifactId("spring-boot-starter-webflux") + .version(getSpringBootVersion()) + .build())); + when(context.getProjectClassLoaders().isClassInCompileClasspath(true, REQUIRED_CLASSES)) + .thenReturn(true); + } + + @Nested + @DisplayName("spring.webflux.base-path property configured") + class SpringWebFluxBasePathConfigured { + @BeforeEach + void setUp() { + props.put("spring.webflux.base-path", "/webflux"); + } + + @AfterEach + void tearDown() { + props.clear(); + } + + @Test + @DisplayName("when management server sharing main server port, then liveness, readiness probes use web flux base path in endpoints") + void whenProbesGenerated_thenProbesAddWebFluxBasePath() { + // Given + props.put("management.port", "8383"); + props.put("management.server.port", "8383"); + writeProps(); + SpringBootHealthCheckEnricher enricher = new SpringBootHealthCheckEnricher(context); + // When + Probe livenessProbe = enricher.getLivenessProbe(); + Probe readinessProbe = enricher.getReadinessProbe(); + // Then + assertHTTPGetPathAndPort(livenessProbe, getActuatorDefaultBasePath() + "/health",8383); + assertHTTPGetPathAndPort(readinessProbe,getActuatorDefaultBasePath() + "/health",8383); + } + + @Test + @DisplayName("when a separate management server port configured, then spring.webflux.base-path is ignored") + void whenManagementServerRunningOnDifferentPort_thenProbesDoNotUseWebFluxBasePath() { + // Given + writeProps(); + SpringBootHealthCheckEnricher enricher = new SpringBootHealthCheckEnricher(context); + // When + Probe livenessProbe = enricher.getLivenessProbe(); + Probe readinessProbe = enricher.getReadinessProbe(); + // Then + assertHTTPGetPathAndPort(livenessProbe,"/webflux" + getActuatorDefaultBasePath() + "/health",8080); + assertHTTPGetPathAndPort(readinessProbe,"/webflux" + getActuatorDefaultBasePath() + "/health",8080); + } + } + + @Test + @DisplayName("when no explicit base path configured, then use default base path") + void whenManagementServerRunningOnDifferentPort_thenProbesDoNotUseWebFluxBasePath() { + // Given + writeProps(); + SpringBootHealthCheckEnricher enricher = new SpringBootHealthCheckEnricher(context); + // When + Probe livenessProbe = enricher.getLivenessProbe(); + Probe readinessProbe = enricher.getReadinessProbe(); + // Then + assertHTTPGetPathAndPort(livenessProbe,getActuatorDefaultBasePath() + "/health",8080); + assertHTTPGetPathAndPort(readinessProbe,getActuatorDefaultBasePath() + "/health",8080); + } + } + private void assertHTTPGetPathAndPort(Probe probe, String path, int port) { assertThat(probe).isNotNull() .extracting(Probe::getHttpGet).isNotNull()