Configure health on additional path only when health exposed

Prior to this commit, limiting the exposure to a specific
technology in `ConditionalOnAvailableEndpoint` would not have
any effect because all endpoints would be considered to be available
if the app was running on Cloud Foundry. This caused issues in cases
where beans were meant to be exposed only if the endpoint was actually
exposed.

This commit adds CLOUD_FOUNDRY to the `EndpointExposure`
enum. This allows `ConditionalOnAvailableEndpoint` to limit
by exposure even when the Cloud Foundry platform is active.

Fixes gh-29532
This commit is contained in:
Madhura Bhave 2022-01-26 20:47:59 -08:00
parent 8c5a5f81ff
commit 0597c6831e
10 changed files with 56 additions and 13 deletions

View File

@ -54,7 +54,7 @@ public class CachesEndpointAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(CachesEndpoint.class)
@ConditionalOnAvailableEndpoint(exposure = EndpointExposure.WEB)
@ConditionalOnAvailableEndpoint(exposure = { EndpointExposure.WEB, EndpointExposure.CLOUD_FOUNDRY })
public CachesEndpointWebExtension cachesEndpointWebExtension(CachesEndpoint cachesEndpoint) {
return new CachesEndpointWebExtension(cachesEndpoint);
}

View File

@ -63,7 +63,7 @@ public class ConfigurationPropertiesReportEndpointAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(ConfigurationPropertiesReportEndpoint.class)
@ConditionalOnAvailableEndpoint(exposure = EndpointExposure.WEB)
@ConditionalOnAvailableEndpoint(exposure = { EndpointExposure.WEB, EndpointExposure.CLOUD_FOUNDRY })
public ConfigurationPropertiesReportEndpointWebExtension configurationPropertiesReportEndpointWebExtension(
ConfigurationPropertiesReportEndpoint configurationPropertiesReportEndpoint) {
return new ConfigurationPropertiesReportEndpointWebExtension(configurationPropertiesReportEndpoint);

View File

@ -113,9 +113,6 @@ class OnAvailableEndpointCondition extends SpringBootCondition {
if (!enablementOutcome.isMatch()) {
return enablementOutcome;
}
if (CloudPlatform.CLOUD_FOUNDRY.isActive(environment)) {
return ConditionOutcome.match(message.because("application is running on Cloud Foundry"));
}
Set<EndpointExposure> exposuresToCheck = getExposuresToCheck(conditionAnnotation);
Set<ExposureFilter> exposureFilters = getExposureFilters(environment);
for (ExposureFilter exposureFilter : exposureFilters) {
@ -168,6 +165,9 @@ class OnAvailableEndpointCondition extends SpringBootCondition {
if (environment.getProperty(JMX_ENABLED_KEY, Boolean.class, false)) {
exposureFilters.add(new ExposureFilter(environment, EndpointExposure.JMX));
}
if (CloudPlatform.CLOUD_FOUNDRY.isActive(environment)) {
exposureFilters.add(new ExposureFilter(environment, EndpointExposure.CLOUD_FOUNDRY));
}
exposureFilters.add(new ExposureFilter(environment, EndpointExposure.WEB));
exposureFiltersCache.put(environment, exposureFilters);
}
@ -181,9 +181,16 @@ class OnAvailableEndpointCondition extends SpringBootCondition {
@SuppressWarnings({ "unchecked", "rawtypes" })
private ExposureFilter(Environment environment, EndpointExposure exposure) {
super((Class) ExposableEndpoint.class, environment,
"management.endpoints." + exposure.name().toLowerCase() + ".exposure",
exposure.getDefaultIncludes());
"management.endpoints." + getCanonicalName(exposure) + ".exposure", exposure.getDefaultIncludes());
this.exposure = exposure;
}
private static String getCanonicalName(EndpointExposure exposure) {
if (EndpointExposure.CLOUD_FOUNDRY.equals(exposure)) {
return "cloud-foundry";
}
return exposure.name().toLowerCase();
}
EndpointExposure getExposure() {

View File

@ -32,7 +32,13 @@ public enum EndpointExposure {
/**
* Exposed via a web endpoint.
*/
WEB("health");
WEB("health"),
/**
* Exposed on Cloud Foundry via `/cloudfoundryapplication`.
* @since 2.6.4
*/
CLOUD_FOUNDRY("*");
private final String[] defaultIncludes;

View File

@ -61,7 +61,7 @@ public class EnvironmentEndpointAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(EnvironmentEndpoint.class)
@ConditionalOnAvailableEndpoint(exposure = EndpointExposure.WEB)
@ConditionalOnAvailableEndpoint(exposure = { EndpointExposure.WEB, EndpointExposure.CLOUD_FOUNDRY })
public EnvironmentEndpointWebExtension environmentEndpointWebExtension(EnvironmentEndpoint environmentEndpoint) {
return new EnvironmentEndpointWebExtension(environmentEndpoint);
}

View File

@ -45,7 +45,8 @@ import org.springframework.context.annotation.Configuration;
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.REACTIVE)
@ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class, exposure = EndpointExposure.WEB)
@ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class,
exposure = { EndpointExposure.WEB, EndpointExposure.CLOUD_FOUNDRY })
class HealthEndpointReactiveWebExtensionConfiguration {
@Bean
@ -57,6 +58,7 @@ class HealthEndpointReactiveWebExtensionConfiguration {
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class, exposure = EndpointExposure.WEB)
static class WebFluxAdditionalHealthEndpointPathsConfiguration {
@Bean

View File

@ -65,7 +65,8 @@ import org.springframework.web.servlet.DispatcherServlet;
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnBean(HealthEndpoint.class)
@ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class, exposure = EndpointExposure.WEB)
@ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class,
exposure = { EndpointExposure.WEB, EndpointExposure.CLOUD_FOUNDRY })
class HealthEndpointWebExtensionConfiguration {
@Bean
@ -82,6 +83,7 @@ class HealthEndpointWebExtensionConfiguration {
}
@ConditionalOnBean(DispatcherServlet.class)
@ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class, exposure = EndpointExposure.WEB)
static class MvcAdditionalHealthEndpointPathsConfiguration {
@Bean
@ -97,6 +99,7 @@ class HealthEndpointWebExtensionConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ResourceConfig.class)
@ConditionalOnMissingClass("org.springframework.web.servlet.DispatcherServlet")
@ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class, exposure = EndpointExposure.WEB)
static class JerseyAdditionalHealthEndpointPathsConfiguration {
@Bean

View File

@ -54,7 +54,7 @@ public class QuartzEndpointAutoConfiguration {
@Bean
@ConditionalOnBean(QuartzEndpoint.class)
@ConditionalOnMissingBean
@ConditionalOnAvailableEndpoint(exposure = EndpointExposure.WEB)
@ConditionalOnAvailableEndpoint(exposure = { EndpointExposure.WEB, EndpointExposure.CLOUD_FOUNDRY })
public QuartzEndpointWebExtension quartzEndpointWebExtension(QuartzEndpoint endpoint) {
return new QuartzEndpointWebExtension(endpoint);
}

View File

@ -327,7 +327,8 @@ class ConditionalOnAvailableEndpointTests {
static class ExposureEndpointConfiguration {
@Bean
@ConditionalOnAvailableEndpoint(endpoint = TestEndpoint.class, exposure = EndpointExposure.WEB)
@ConditionalOnAvailableEndpoint(endpoint = TestEndpoint.class,
exposure = { EndpointExposure.WEB, EndpointExposure.CLOUD_FOUNDRY })
String unexposed() {
return "unexposed";
}

View File

@ -91,6 +91,30 @@ abstract class AbstractHealthEndpointAdditionalPathIntegrationTests<T extends Ab
.exchange().expectStatus().isNotFound(), "local.server.port"));
}
@Test
void groupsAreNotConfiguredWhenHealthEndpointIsNotExposedAndCloudFoundryPlatform() {
this.runner.withPropertyValues("spring.jmx.enabled=true", "management.endpoints.web.exposure.exclude=health",
"spring.main.cloud-platform=cloud_foundry", "management.endpoint.health.group.live.include=diskSpace",
"management.endpoint.health.group.live.additional-path=server:healthz",
"management.endpoint.health.group.live.show-components=always")
.withInitializer(new ConditionEvaluationReportLoggingListener())
.run(withWebTestClient((client) -> client.get().uri("/healthz").accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isNotFound(), "local.server.port"));
}
@Test
void groupsAreNotConfiguredWhenHealthEndpointIsNotExposedWithDifferentManagementPortAndCloudFoundryPlatform() {
this.runner
.withPropertyValues("spring.jmx.enabled=true", "management.endpoints.web.exposure.exclude=health",
"spring.main.cloud-platform=cloud_foundry", "management.server.port=0",
"management.endpoint.health.group.live.include=diskSpace",
"management.endpoint.health.group.live.additional-path=server:healthz",
"management.endpoint.health.group.live.show-components=always")
.withInitializer(new ConditionEvaluationReportLoggingListener())
.run(withWebTestClient((client) -> client.get().uri("/healthz").accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isNotFound(), "local.server.port"));
}
private void testResponse(WebTestClient client) {
client.get().uri("/healthz").accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
.jsonPath("status").isEqualTo("UP").jsonPath("components.diskSpace").exists();