Auto-configure health web components only if endpoint is exposed over HTTP
Fixes gh-28131 Co-authored-by: Phillip Webb <pwebb@vmware.com>
This commit is contained in:
@@ -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 Set<Exposure> getExposures(Environment environment) {
|
||||
Set<Exposure> exposures = exposuresCache.get(environment);
|
||||
if (exposures == null) {
|
||||
exposures = new HashSet<>(2);
|
||||
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<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();
|
||||
|
||||
Reference in New Issue
Block a user