Rework Cloud Foundry actuator support behind a pluggable abstraction

Deprecate `EndpointExposure.CLOUD_FOUNDRY` and introduce an alternative
implementation based on a pluggable abstraction.

The new `EndpointExposureOutcomeContributor` interface may now be used
to influence `@OnAvailableEndpointCondition` exposure results. Several
infrastructure beans that previously used the condition have been
refactored to always be registered, but tolerate missing endpoints.

A new smoke test application has been added that demonstrates how the
abstraction can be used by a third-party.

Closes gh-41135

Co-authored-by: Phillip Webb <phil.webb@broadcom.com>
This commit is contained in:
Andy Wilkinson
2024-08-20 07:31:01 -07:00
committed by Phillip Webb
parent cbb738338d
commit 73f71d5560
30 changed files with 698 additions and 122 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2012-2024 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.
@@ -16,6 +16,7 @@
package org.springframework.boot.actuate.endpoint.web.reactive;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
@@ -41,21 +42,28 @@ public class AdditionalHealthEndpointPathsWebFluxHandlerMapping extends Abstract
private final EndpointMapping endpointMapping;
private final ExposableWebEndpoint endpoint;
private final ExposableWebEndpoint healthEndpoint;
private final Set<HealthEndpointGroup> groups;
public AdditionalHealthEndpointPathsWebFluxHandlerMapping(EndpointMapping endpointMapping,
ExposableWebEndpoint endpoint, Set<HealthEndpointGroup> groups) {
super(endpointMapping, Collections.singletonList(endpoint), null, null, false);
ExposableWebEndpoint healthEndpoint, Set<HealthEndpointGroup> groups) {
super(endpointMapping, asList(healthEndpoint), null, null, false);
this.endpointMapping = endpointMapping;
this.groups = groups;
this.endpoint = endpoint;
this.healthEndpoint = healthEndpoint;
}
private static Collection<ExposableWebEndpoint> asList(ExposableWebEndpoint healthEndpoint) {
return (healthEndpoint != null) ? Collections.singletonList(healthEndpoint) : Collections.emptyList();
}
@Override
protected void initHandlerMethods() {
for (WebOperation operation : this.endpoint.getOperations()) {
if (this.healthEndpoint == null) {
return;
}
for (WebOperation operation : this.healthEndpoint.getOperations()) {
WebOperationRequestPredicate predicate = operation.getRequestPredicate();
String matchAllRemainingPathSegmentsVariable = predicate.getMatchAllRemainingPathSegmentsVariable();
if (matchAllRemainingPathSegmentsVariable != null) {
@@ -64,7 +72,7 @@ public class AdditionalHealthEndpointPathsWebFluxHandlerMapping extends Abstract
if (additionalPath != null) {
RequestMappingInfo requestMappingInfo = getRequestMappingInfo(operation,
additionalPath.getValue());
registerReadMapping(requestMappingInfo, this.endpoint, operation);
registerReadMapping(requestMappingInfo, this.healthEndpoint, operation);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2012-2024 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.
@@ -16,6 +16,7 @@
package org.springframework.boot.actuate.endpoint.web.servlet;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
@@ -36,27 +37,34 @@ import org.springframework.web.servlet.HandlerMapping;
*/
public class AdditionalHealthEndpointPathsWebMvcHandlerMapping extends AbstractWebMvcEndpointHandlerMapping {
private final ExposableWebEndpoint endpoint;
private final ExposableWebEndpoint healthEndpoint;
private final Set<HealthEndpointGroup> groups;
public AdditionalHealthEndpointPathsWebMvcHandlerMapping(ExposableWebEndpoint endpoint,
public AdditionalHealthEndpointPathsWebMvcHandlerMapping(ExposableWebEndpoint healthEndpoint,
Set<HealthEndpointGroup> groups) {
super(new EndpointMapping(""), Collections.singletonList(endpoint), null, false);
this.endpoint = endpoint;
super(new EndpointMapping(""), asList(healthEndpoint), null, false);
this.healthEndpoint = healthEndpoint;
this.groups = groups;
}
private static Collection<ExposableWebEndpoint> asList(ExposableWebEndpoint healthEndpoint) {
return (healthEndpoint != null) ? Collections.singletonList(healthEndpoint) : Collections.emptyList();
}
@Override
protected void initHandlerMethods() {
for (WebOperation operation : this.endpoint.getOperations()) {
if (this.healthEndpoint == null) {
return;
}
for (WebOperation operation : this.healthEndpoint.getOperations()) {
WebOperationRequestPredicate predicate = operation.getRequestPredicate();
String matchAllRemainingPathSegmentsVariable = predicate.getMatchAllRemainingPathSegmentsVariable();
if (matchAllRemainingPathSegmentsVariable != null) {
for (HealthEndpointGroup group : this.groups) {
AdditionalHealthEndpointPath additionalPath = group.getAdditionalPath();
if (additionalPath != null) {
registerMapping(this.endpoint, predicate, operation, additionalPath.getValue());
registerMapping(this.healthEndpoint, predicate, operation, additionalPath.getValue());
}
}
}