Auto-configure health web components only if endpoint is exposed over HTTP

Fixes gh-28131

Co-authored-by: Phillip Webb <pwebb@vmware.com>
pull/28398/head
Madhura Bhave 3 years ago
parent 42d21a8336
commit b7521e2fda

@ -1,124 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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.endpoint.condition;
import java.lang.annotation.Annotation;
import java.util.Map;
import java.util.Optional;
import org.springframework.boot.actuate.endpoint.EndpointId;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
/**
* Base class for {@link Endpoint @Endpoint} related {@link SpringBootCondition}
* implementations.
*
* @author Stephane Nicoll
* @author Andy Wilkinson
* @author Phillip Webb
*/
abstract class AbstractEndpointCondition extends SpringBootCondition {
private static final String ENABLED_BY_DEFAULT_KEY = "management.endpoints.enabled-by-default";
private static final ConcurrentReferenceHashMap<Environment, Optional<Boolean>> enabledByDefaultCache = new ConcurrentReferenceHashMap<>();
AnnotationAttributes getEndpointAttributes(Class<?> annotationClass, ConditionContext context,
AnnotatedTypeMetadata metadata) {
return getEndpointAttributes(getEndpointType(annotationClass, context, metadata));
}
protected ConditionOutcome getEnablementOutcome(ConditionContext context, AnnotatedTypeMetadata metadata,
Class<? extends Annotation> annotationClass) {
Environment environment = context.getEnvironment();
AnnotationAttributes attributes = getEndpointAttributes(annotationClass, context, metadata);
EndpointId id = EndpointId.of(environment, attributes.getString("id"));
String key = "management.endpoint." + id.toLowerCaseString() + ".enabled";
Boolean userDefinedEnabled = environment.getProperty(key, Boolean.class);
if (userDefinedEnabled != null) {
return new ConditionOutcome(userDefinedEnabled, ConditionMessage.forCondition(annotationClass)
.because("found property " + key + " with value " + userDefinedEnabled));
}
Boolean userDefinedDefault = isEnabledByDefault(environment);
if (userDefinedDefault != null) {
return new ConditionOutcome(userDefinedDefault, ConditionMessage.forCondition(annotationClass).because(
"no property " + key + " found so using user defined default from " + ENABLED_BY_DEFAULT_KEY));
}
boolean endpointDefault = attributes.getBoolean("enableByDefault");
return new ConditionOutcome(endpointDefault, ConditionMessage.forCondition(annotationClass)
.because("no property " + key + " found so using endpoint default"));
}
protected Class<?> getEndpointType(Class<?> annotationClass, ConditionContext context,
AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(annotationClass.getName());
if (attributes != null && attributes.containsKey("endpoint")) {
Class<?> target = (Class<?>) attributes.get("endpoint");
if (target != Void.class) {
return target;
}
}
Assert.state(metadata instanceof MethodMetadata && metadata.isAnnotated(Bean.class.getName()),
"EndpointCondition must be used on @Bean methods when the endpoint is not specified");
MethodMetadata methodMetadata = (MethodMetadata) metadata;
try {
return ClassUtils.forName(methodMetadata.getReturnTypeName(), context.getClassLoader());
}
catch (Throwable ex) {
throw new IllegalStateException("Failed to extract endpoint id for "
+ methodMetadata.getDeclaringClassName() + "." + methodMetadata.getMethodName(), ex);
}
}
protected AnnotationAttributes getEndpointAttributes(Class<?> type) {
MergedAnnotations annotations = MergedAnnotations.from(type, SearchStrategy.TYPE_HIERARCHY);
MergedAnnotation<Endpoint> endpoint = annotations.get(Endpoint.class);
if (endpoint.isPresent()) {
return endpoint.asAnnotationAttributes();
}
MergedAnnotation<EndpointExtension> extension = annotations.get(EndpointExtension.class);
Assert.state(extension.isPresent(), "No endpoint is specified and the return type of the @Bean method is "
+ "neither an @Endpoint, nor an @EndpointExtension");
return getEndpointAttributes(extension.getClass("endpoint"));
}
private Boolean isEnabledByDefault(Environment environment) {
Optional<Boolean> enabledByDefault = enabledByDefaultCache.get(environment);
if (enabledByDefault == null) {
enabledByDefault = Optional.ofNullable(environment.getProperty(ENABLED_BY_DEFAULT_KEY, Boolean.class));
enabledByDefaultCache.put(environment, enabledByDefault);
}
return enabledByDefault.orElse(null);
}
}

@ -22,6 +22,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension;
import org.springframework.context.annotation.Conditional;
@ -29,8 +30,9 @@ import org.springframework.core.env.Environment;
/**
* {@link Conditional @Conditional} that checks whether an endpoint is available. An
* endpoint is considered available if it is both enabled and exposed. Matches enablement
* according to the endpoints specific {@link Environment} property, falling back to
* endpoint is considered available if it is both enabled and exposed on the specified
* technologies. Matches enablement according to the endpoints specific
* {@link Environment} property, falling back to
* {@code management.endpoints.enabled-by-default} or failing that
* {@link Endpoint#enableByDefault()}. Matches exposure according to any of the
* {@code management.endpoints.web.exposure.<id>} or
@ -112,4 +114,12 @@ public @interface ConditionalOnAvailableEndpoint {
*/
Class<?> endpoint() default Void.class;
/**
* Technologies to check the exposure of the endpoint on while considering it to be
* available.
* @return the technologies to check
* @since 2.6.0
*/
EndpointExposure[] exposure() default {};
}

@ -16,20 +16,34 @@
package org.springframework.boot.actuate.autoconfigure.endpoint.condition;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure;
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludeExcludeEndpointFilter;
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludeExcludeEndpointFilter.DefaultIncludes;
import org.springframework.boot.actuate.endpoint.EndpointId;
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
/**
@ -40,66 +54,140 @@ import org.springframework.util.ConcurrentReferenceHashMap;
* @author Phillip Webb
* @see ConditionalOnAvailableEndpoint
*/
class OnAvailableEndpointCondition extends AbstractEndpointCondition {
class OnAvailableEndpointCondition extends SpringBootCondition {
private static final String JMX_ENABLED_KEY = "spring.jmx.enabled";
private static final Map<Environment, Set<Exposure>> exposuresCache = new ConcurrentReferenceHashMap<>();
private static final String ENABLED_BY_DEFAULT_KEY = "management.endpoints.enabled-by-default";
private static final Map<Environment, Set<ExposureFilter>> exposureFiltersCache = new ConcurrentReferenceHashMap<>();
private static final ConcurrentReferenceHashMap<Environment, Optional<Boolean>> enabledByDefaultCache = new ConcurrentReferenceHashMap<>();
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionOutcome enablementOutcome = getEnablementOutcome(context, metadata,
ConditionalOnAvailableEndpoint.class);
Environment environment = context.getEnvironment();
MergedAnnotation<ConditionalOnAvailableEndpoint> conditionAnnotation = metadata.getAnnotations()
.get(ConditionalOnAvailableEndpoint.class);
Class<?> target = getTarget(context, metadata, conditionAnnotation);
MergedAnnotation<Endpoint> endpointAnnotation = getEndpointAnnotation(target);
return getMatchOutcome(environment, conditionAnnotation, endpointAnnotation);
}
private Class<?> getTarget(ConditionContext context, AnnotatedTypeMetadata metadata,
MergedAnnotation<ConditionalOnAvailableEndpoint> condition) {
Class<?> target = condition.getClass("endpoint");
if (target != Void.class) {
return target;
}
Assert.state(metadata instanceof MethodMetadata && metadata.isAnnotated(Bean.class.getName()),
"EndpointCondition must be used on @Bean methods when the endpoint is not specified");
MethodMetadata methodMetadata = (MethodMetadata) metadata;
try {
return ClassUtils.forName(methodMetadata.getReturnTypeName(), context.getClassLoader());
}
catch (Throwable ex) {
throw new IllegalStateException("Failed to extract endpoint id for "
+ methodMetadata.getDeclaringClassName() + "." + methodMetadata.getMethodName(), ex);
}
}
protected MergedAnnotation<Endpoint> getEndpointAnnotation(Class<?> target) {
MergedAnnotations annotations = MergedAnnotations.from(target, SearchStrategy.TYPE_HIERARCHY);
MergedAnnotation<Endpoint> endpoint = annotations.get(Endpoint.class);
if (endpoint.isPresent()) {
return endpoint;
}
MergedAnnotation<EndpointExtension> extension = annotations.get(EndpointExtension.class);
Assert.state(extension.isPresent(), "No endpoint is specified and the return type of the @Bean method is "
+ "neither an @Endpoint, nor an @EndpointExtension");
return getEndpointAnnotation(extension.getClass("endpoint"));
}
private ConditionOutcome getMatchOutcome(Environment environment,
MergedAnnotation<ConditionalOnAvailableEndpoint> conditionAnnotation,
MergedAnnotation<Endpoint> endpointAnnotation) {
ConditionMessage.Builder message = ConditionMessage.forCondition(ConditionalOnAvailableEndpoint.class);
EndpointId endpointId = EndpointId.of(environment, endpointAnnotation.getString("id"));
ConditionOutcome enablementOutcome = getEnablementOutcome(environment, endpointAnnotation, endpointId, message);
if (!enablementOutcome.isMatch()) {
return enablementOutcome;
}
ConditionMessage message = enablementOutcome.getConditionMessage();
Environment environment = context.getEnvironment();
if (CloudPlatform.CLOUD_FOUNDRY.isActive(environment)) {
return new ConditionOutcome(true, message.andCondition(ConditionalOnAvailableEndpoint.class)
.because("application is running on Cloud Foundry"));
return ConditionOutcome.match(message.because("application is running on Cloud Foundry"));
}
EndpointId id = EndpointId.of(environment,
getEndpointAttributes(ConditionalOnAvailableEndpoint.class, context, metadata).getString("id"));
Set<Exposure> exposures = getExposures(environment);
for (Exposure exposure : exposures) {
if (exposure.isExposed(id)) {
return new ConditionOutcome(true,
message.andCondition(ConditionalOnAvailableEndpoint.class)
.because("marked as exposed by a 'management.endpoints." + exposure.getPrefix()
+ ".exposure' property"));
Set<EndpointExposure> exposuresToCheck = getExposuresToCheck(conditionAnnotation);
Set<ExposureFilter> exposureFilters = getExposureFilters(environment);
for (ExposureFilter exposureFilter : exposureFilters) {
if (exposuresToCheck.contains(exposureFilter.getExposure()) && exposureFilter.isExposed(endpointId)) {
return ConditionOutcome.match(message.because("marked as exposed by a 'management.endpoints."
+ exposureFilter.getExposure().name().toLowerCase() + ".exposure' property"));
}
}
return new ConditionOutcome(false, message.andCondition(ConditionalOnAvailableEndpoint.class)
.because("no 'management.endpoints' property marked it as exposed"));
return ConditionOutcome.noMatch(message.because("no 'management.endpoints' property marked it as exposed"));
}
private ConditionOutcome getEnablementOutcome(Environment environment,
MergedAnnotation<Endpoint> endpointAnnotation, EndpointId endpointId, ConditionMessage.Builder message) {
String key = "management.endpoint." + endpointId.toLowerCaseString() + ".enabled";
Boolean userDefinedEnabled = environment.getProperty(key, Boolean.class);
if (userDefinedEnabled != null) {
return new ConditionOutcome(userDefinedEnabled,
message.because("found property " + key + " with value " + userDefinedEnabled));
}
Boolean userDefinedDefault = isEnabledByDefault(environment);
if (userDefinedDefault != null) {
return new ConditionOutcome(userDefinedDefault, message.because(
"no property " + key + " found so using user defined default from " + ENABLED_BY_DEFAULT_KEY));
}
boolean endpointDefault = endpointAnnotation.getBoolean("enableByDefault");
return new ConditionOutcome(endpointDefault,
message.because("no property " + key + " found so using endpoint default of " + endpointDefault));
}
private Boolean isEnabledByDefault(Environment environment) {
Optional<Boolean> enabledByDefault = enabledByDefaultCache.get(environment);
if (enabledByDefault == null) {
enabledByDefault = Optional.ofNullable(environment.getProperty(ENABLED_BY_DEFAULT_KEY, Boolean.class));
enabledByDefaultCache.put(environment, enabledByDefault);
}
return enabledByDefault.orElse(null);
}
private Set<EndpointExposure> getExposuresToCheck(
MergedAnnotation<ConditionalOnAvailableEndpoint> conditionAnnotation) {
EndpointExposure[] exposure = conditionAnnotation.getEnumArray("exposure", EndpointExposure.class);
return (exposure.length == 0) ? EnumSet.allOf(EndpointExposure.class)
: new LinkedHashSet<>(Arrays.asList(exposure));
}
private Set<Exposure> getExposures(Environment environment) {
Set<Exposure> exposures = exposuresCache.get(environment);
if (exposures == null) {
exposures = new HashSet<>(2);
private Set<ExposureFilter> getExposureFilters(Environment environment) {
Set<ExposureFilter> exposureFilters = exposureFiltersCache.get(environment);
if (exposureFilters == null) {
exposureFilters = new HashSet<>(2);
if (environment.getProperty(JMX_ENABLED_KEY, Boolean.class, false)) {
exposures.add(new Exposure(environment, "jmx", DefaultIncludes.JMX));
exposureFilters.add(new ExposureFilter(environment, EndpointExposure.JMX));
}
exposures.add(new Exposure(environment, "web", DefaultIncludes.WEB));
exposuresCache.put(environment, exposures);
exposureFilters.add(new ExposureFilter(environment, EndpointExposure.WEB));
exposureFiltersCache.put(environment, exposureFilters);
}
return exposures;
return exposureFilters;
}
static class Exposure extends IncludeExcludeEndpointFilter<ExposableEndpoint<?>> {
static final class ExposureFilter extends IncludeExcludeEndpointFilter<ExposableEndpoint<?>> {
private final String prefix;
private final EndpointExposure exposure;
@SuppressWarnings({ "rawtypes", "unchecked" })
Exposure(Environment environment, String prefix, DefaultIncludes defaultIncludes) {
super((Class) ExposableEndpoint.class, environment, "management.endpoints." + prefix + ".exposure",
defaultIncludes);
this.prefix = prefix;
@SuppressWarnings({ "unchecked", "rawtypes" })
private ExposureFilter(Environment environment, EndpointExposure exposure) {
super((Class) ExposableEndpoint.class, environment,
"management.endpoints." + exposure.name().toLowerCase() + ".exposure",
exposure.getDefaultIncludes());
this.exposure = exposure;
}
String getPrefix() {
return this.prefix;
EndpointExposure getExposure() {
return this.exposure;
}
boolean isExposed(EndpointId id) {

@ -0,0 +1,51 @@
/*
* Copyright 2012-2021 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
*
* https://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.endpoint.expose;
/**
* Technologies that can be used to expose an endpoint.
*
* @author Phillip Webb
* @since 2.6.0
*/
public enum EndpointExposure {
/**
* Exposed via JMX endpoint.
*/
JMX("*"),
/**
* Exposed via a web endpoint.
*/
WEB("health");
private final String[] defaultIncludes;
EndpointExposure(String... defaultIncludes) {
this.defaultIncludes = defaultIncludes;
}
/**
* Return the default set of include patterns.
* @return the default includes
*/
public String[] getDefaultIncludes() {
return this.defaultIncludes;
}
}

@ -58,10 +58,13 @@ public class IncludeExcludeEndpointFilter<E extends ExposableEndpoint<?>> implem
* @param environment the environment containing the properties
* @param prefix the property prefix to bind
* @param defaultIncludes the default {@code includes} to use when none are specified.
* @deprecated since 2.6.0 for removal in 2.8.0 in favor of
* {@link #IncludeExcludeEndpointFilter(Class, Environment, String, String[])}
*/
@Deprecated
public IncludeExcludeEndpointFilter(Class<E> endpointType, Environment environment, String prefix,
String... defaultIncludes) {
this(endpointType, environment, prefix, new EndpointPatterns(defaultIncludes));
DefaultIncludes defaultIncludes) {
this(endpointType, environment, prefix, DefaultIncludes.patterns(defaultIncludes));
}
/**
@ -74,21 +77,8 @@ public class IncludeExcludeEndpointFilter<E extends ExposableEndpoint<?>> implem
* @param defaultIncludes the default {@code includes} to use when none are specified.
*/
public IncludeExcludeEndpointFilter(Class<E> endpointType, Environment environment, String prefix,
DefaultIncludes defaultIncludes) {
this(endpointType, environment, prefix, DefaultIncludes.patterns(defaultIncludes));
}
private IncludeExcludeEndpointFilter(Class<E> endpointType, Environment environment, String prefix,
EndpointPatterns defaultIncludes) {
Assert.notNull(endpointType, "EndpointType must not be null");
Assert.notNull(environment, "Environment must not be null");
Assert.hasText(prefix, "Prefix must not be empty");
Assert.notNull(defaultIncludes, "DefaultIncludes must not be null");
Binder binder = Binder.get(environment);
this.endpointType = endpointType;
this.include = new EndpointPatterns(bind(binder, prefix + ".include"));
this.defaultIncludes = defaultIncludes;
this.exclude = new EndpointPatterns(bind(binder, prefix + ".exclude"));
String... defaultIncludes) {
this(endpointType, environment, prefix, new EndpointPatterns(defaultIncludes));
}
/**
@ -113,12 +103,27 @@ public class IncludeExcludeEndpointFilter<E extends ExposableEndpoint<?>> implem
* @param include the include patterns
* @param exclude the exclude patterns
* @param defaultIncludes the default {@code includes} to use when none are specified.
* @deprecated since 2.6.0 for removal in 2.8.0 in favor of
* {@link #IncludeExcludeEndpointFilter(Class, Environment, String, String[])}
*/
public IncludeExcludeEndpointFilter(Class<E> endpointType, Collection<String> include, Collection<String> exclude,
DefaultIncludes defaultIncludes) {
this(endpointType, include, exclude, DefaultIncludes.patterns(defaultIncludes));
}
private IncludeExcludeEndpointFilter(Class<E> endpointType, Environment environment, String prefix,
EndpointPatterns defaultIncludes) {
Assert.notNull(endpointType, "EndpointType must not be null");
Assert.notNull(environment, "Environment must not be null");
Assert.hasText(prefix, "Prefix must not be empty");
Assert.notNull(defaultIncludes, "DefaultIncludes must not be null");
Binder binder = Binder.get(environment);
this.endpointType = endpointType;
this.include = new EndpointPatterns(bind(binder, prefix + ".include"));
this.defaultIncludes = defaultIncludes;
this.exclude = new EndpointPatterns(bind(binder, prefix + ".exclude"));
}
private IncludeExcludeEndpointFilter(Class<E> endpointType, Collection<String> include, Collection<String> exclude,
EndpointPatterns defaultIncludes) {
Assert.notNull(endpointType, "EndpointType Type must not be null");
@ -146,8 +151,9 @@ public class IncludeExcludeEndpointFilter<E extends ExposableEndpoint<?>> implem
* Return {@code true} if the filter matches.
* @param endpointId the endpoint ID to check
* @return {@code true} if the filter matches
* @since 2.6.0
*/
protected final boolean match(EndpointId endpointId) {
public final boolean match(EndpointId endpointId) {
return isIncluded(endpointId) && !isExcluded(endpointId);
}
@ -167,7 +173,9 @@ public class IncludeExcludeEndpointFilter<E extends ExposableEndpoint<?>> implem
/**
* Default include patterns that can be used.
* @deprecated since 2.6.0 for removal in 2.8.0 in favor of {@link EndpointExposure}.
*/
@Deprecated
public enum DefaultIncludes {
/**
@ -232,6 +240,10 @@ public class IncludeExcludeEndpointFilter<E extends ExposableEndpoint<?>> implem
return this.matchesAll || this.endpointIds.contains(endpointId);
}
static EndpointPatterns forExposure(EndpointExposure exposure) {
return (exposure != null) ? new EndpointPatterns(exposure.getDefaultIncludes()) : null;
}
}
}

@ -25,6 +25,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.LazyInitializationExcludeFilter;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure;
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludeExcludeEndpointFilter;
import org.springframework.boot.actuate.endpoint.EndpointFilter;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
@ -108,7 +109,7 @@ public class JmxEndpointAutoConfiguration {
public IncludeExcludeEndpointFilter<ExposableJmxEndpoint> jmxIncludeExcludePropertyEndpointFilter() {
JmxEndpointProperties.Exposure exposure = this.properties.getExposure();
return new IncludeExcludeEndpointFilter<>(ExposableJmxEndpoint.class, exposure.getInclude(),
exposure.getExclude(), "*");
exposure.getExclude(), EndpointExposure.JMX.getDefaultIncludes());
}
@Bean

@ -22,8 +22,8 @@ import java.util.stream.Collectors;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure;
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludeExcludeEndpointFilter;
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludeExcludeEndpointFilter.DefaultIncludes;
import org.springframework.boot.actuate.endpoint.EndpointFilter;
import org.springframework.boot.actuate.endpoint.EndpointsSupplier;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
@ -116,7 +116,7 @@ public class WebEndpointAutoConfiguration {
public IncludeExcludeEndpointFilter<ExposableWebEndpoint> webExposeExcludePropertyEndpointFilter() {
WebEndpointProperties.Exposure exposure = this.properties.getExposure();
return new IncludeExcludeEndpointFilter<>(ExposableWebEndpoint.class, exposure.getInclude(),
exposure.getExclude(), DefaultIncludes.WEB);
exposure.getExclude(), EndpointExposure.WEB.getDefaultIncludes());
}
@Bean

@ -27,6 +27,8 @@ import java.util.stream.Collectors;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.model.Resource;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.jersey.ManagementContextResourceConfigCustomizer;
@ -86,8 +88,8 @@ class JerseyWebEndpointManagementContextConfiguration {
}
@Bean
@ConditionalOnBean(HealthEndpoint.class)
@ConditionalOnManagementPort(ManagementPortType.DIFFERENT)
@ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class, exposure = EndpointExposure.WEB)
JerseyAdditionalHealthEndpointPathsManagementResourcesRegistrar jerseyDifferentPortAdditionalHealthEndpointPathsResourcesRegistrar(
WebEndpointsSupplier webEndpointsSupplier, HealthEndpointGroups healthEndpointGroups) {
Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();

@ -20,6 +20,8 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.CorsEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
@ -91,6 +93,7 @@ public class WebFluxEndpointManagementContextConfiguration {
@Bean
@ConditionalOnManagementPort(ManagementPortType.DIFFERENT)
@ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class, exposure = EndpointExposure.WEB)
@ConditionalOnBean(HealthEndpoint.class)
public AdditionalHealthEndpointPathsWebFluxHandlerMapping managementHealthEndpointWebFluxHandlerMapping(
WebEndpointsSupplier webEndpointsSupplier, HealthEndpointGroups groups) {

@ -20,6 +20,8 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.CorsEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
@ -95,6 +97,7 @@ public class WebMvcEndpointManagementContextConfiguration {
@Bean
@ConditionalOnManagementPort(ManagementPortType.DIFFERENT)
@ConditionalOnBean(HealthEndpoint.class)
@ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class, exposure = EndpointExposure.WEB)
public AdditionalHealthEndpointPathsWebMvcHandlerMapping managementHealthEndpointWebMvcHandlerMapping(
WebEndpointsSupplier webEndpointsSupplier, HealthEndpointGroups groups) {
Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();

@ -18,6 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.health;
import java.util.Collection;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure;
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier;
@ -43,7 +45,7 @@ import org.springframework.context.annotation.Configuration;
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.REACTIVE)
@ConditionalOnBean(HealthEndpoint.class)
@ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class, exposure = EndpointExposure.WEB)
class HealthEndpointReactiveWebExtensionConfiguration {
@Bean

@ -27,6 +27,8 @@ import org.glassfish.jersey.server.model.Resource;
import org.glassfish.jersey.servlet.ServletContainer;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure;
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier;
@ -62,7 +64,7 @@ import org.springframework.web.servlet.DispatcherServlet;
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnBean(HealthEndpoint.class)
@ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class, exposure = EndpointExposure.WEB)
class HealthEndpointWebExtensionConfiguration {
@Bean
@ -79,7 +81,6 @@ class HealthEndpointWebExtensionConfiguration {
.findFirst().get();
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(DispatcherServlet.class)
static class MvcAdditionalHealthEndpointPathsConfiguration {

@ -18,6 +18,7 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.condition;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure;
import org.springframework.boot.actuate.endpoint.EndpointFilter;
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
@ -196,6 +197,14 @@ class ConditionalOnAvailableEndpointTests {
.run((context) -> assertThat(context).hasSingleBean(DashedEndpoint.class));
}
@Test
void outcomeWhenEndpointNotExposedOnSpecifiedTechnology() {
this.contextRunner.withUserConfiguration(ExposureEndpointConfiguration.class)
.withPropertyValues("spring.jmx.enabled=true", "management.endpoints.jmx.exposure.include=test",
"management.endpoints.web.exposure.exclude=test")
.run((context) -> assertThat(context).doesNotHaveBean("unexposed"));
}
@Endpoint(id = "health")
static class HealthEndpoint {
@ -314,4 +323,15 @@ class ConditionalOnAvailableEndpointTests {
}
@Configuration(proxyBeanMethods = false)
static class ExposureEndpointConfiguration {
@Bean
@ConditionalOnAvailableEndpoint(endpoint = TestEndpoint.class, exposure = EndpointExposure.WEB)
String unexposed() {
return "unexposed";
}
}
}

@ -79,6 +79,18 @@ abstract class AbstractHealthEndpointAdditionalPathIntegrationTests<T extends Ab
.run(withWebTestClient(this::testResponse, "local.server.port"));
}
@Test
void groupsAreNotConfiguredWhenHealthEndpointIsNotExposed() {
this.runner
.withPropertyValues("spring.jmx.enabled=true", "management.endpoints.web.exposure.exclude=health",
"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();

Loading…
Cancel
Save