Skip to content

Commit 7afb161

Browse files
mbhavephilwebb
authored andcommitted
Add CloudFoundry EndpointHandlerMapping
Add a CloudFoundryEndpointHandlerMapping that can expose actuator endpoints for Cloud Foundry "appsmanager" to use. See gh-7108
1 parent 570b292 commit 7afb161

File tree

10 files changed

+388
-34
lines changed

10 files changed

+388
-34
lines changed

spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementWebSecurityAutoConfiguration.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,8 @@ private EndpointHandlerMapping getRequiredEndpointHandlerMapping() {
406406
EndpointHandlerMapping endpointHandlerMapping = null;
407407
ApplicationContext context = this.contextResolver.getApplicationContext();
408408
if (context.getBeanNamesForType(EndpointHandlerMapping.class).length > 0) {
409-
endpointHandlerMapping = context.getBean(EndpointHandlerMapping.class);
409+
endpointHandlerMapping = context.getBean("endpointHandlerMapping",
410+
EndpointHandlerMapping.class);
410411
}
411412
if (endpointHandlerMapping == null) {
412413
// Maybe there are actually no endpoints (e.g. management.port=-1)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2012-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.cloudfoundry;
18+
19+
import java.util.LinkedHashSet;
20+
import java.util.Set;
21+
22+
import org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration;
23+
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints;
24+
import org.springframework.boot.actuate.endpoint.mvc.NamedMvcEndpoint;
25+
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
26+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
27+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
28+
import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform;
29+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
30+
import org.springframework.boot.cloud.CloudPlatform;
31+
import org.springframework.context.annotation.Bean;
32+
import org.springframework.context.annotation.Configuration;
33+
import org.springframework.web.cors.CorsConfiguration;
34+
35+
/**
36+
* {@link EnableAutoConfiguration Auto-configuration} to expose actuator endpoints for
37+
* cloud foundry to use.
38+
*
39+
* @author Madhura Bhave
40+
* @since 1.5.0
41+
*/
42+
@Configuration
43+
@ConditionalOnProperty(prefix = "management.cloudfoundry", name = "enabled", matchIfMissing = false)
44+
@ConditionalOnBean(MvcEndpoints.class)
45+
@AutoConfigureAfter(EndpointWebMvcAutoConfiguration.class)
46+
@ConditionalOnCloudPlatform(CloudPlatform.CLOUD_FOUNDRY)
47+
public class CloudFoundryActuatorAutoConfiguration {
48+
49+
@Bean
50+
public CloudFoundryEndpointHandlerMapping cloudFoundryEndpointHandlerMapping(
51+
MvcEndpoints mvcEndpoints) {
52+
Set<NamedMvcEndpoint> endpoints = new LinkedHashSet<NamedMvcEndpoint>(
53+
mvcEndpoints.getEndpoints(NamedMvcEndpoint.class));
54+
CloudFoundryEndpointHandlerMapping mapping = new CloudFoundryEndpointHandlerMapping(
55+
endpoints, getCorsConfiguration());
56+
mapping.setPrefix("/cloudfoundryapplication");
57+
return mapping;
58+
}
59+
60+
private CorsConfiguration getCorsConfiguration() {
61+
CorsConfiguration corsConfiguration = new CorsConfiguration();
62+
corsConfiguration.addAllowedOrigin(CorsConfiguration.ALL);
63+
return corsConfiguration;
64+
}
65+
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright 2012-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.cloudfoundry;
18+
19+
import java.util.ArrayList;
20+
import java.util.Arrays;
21+
import java.util.Collection;
22+
import java.util.List;
23+
import java.util.Set;
24+
25+
import javax.servlet.http.HttpServletRequest;
26+
import javax.servlet.http.HttpServletResponse;
27+
28+
import org.springframework.boot.actuate.endpoint.Endpoint;
29+
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping;
30+
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
31+
import org.springframework.boot.actuate.endpoint.mvc.NamedMvcEndpoint;
32+
import org.springframework.web.cors.CorsConfiguration;
33+
import org.springframework.web.servlet.HandlerExecutionChain;
34+
import org.springframework.web.servlet.HandlerInterceptor;
35+
import org.springframework.web.servlet.HandlerMapping;
36+
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
37+
38+
/**
39+
* {@link HandlerMapping} to map {@link Endpoint}s to Cloud Foundry specific URLs.
40+
*
41+
* @author Madhura Bhave
42+
*/
43+
class CloudFoundryEndpointHandlerMapping extends EndpointHandlerMapping {
44+
45+
CloudFoundryEndpointHandlerMapping(Collection<? extends NamedMvcEndpoint> endpoints) {
46+
super(endpoints);
47+
}
48+
49+
CloudFoundryEndpointHandlerMapping(Set<NamedMvcEndpoint> endpoints,
50+
CorsConfiguration corsConfiguration) {
51+
super(endpoints, corsConfiguration);
52+
}
53+
54+
@Override
55+
protected String getPath(MvcEndpoint endpoint) {
56+
if (endpoint instanceof NamedMvcEndpoint) {
57+
return "/" + ((NamedMvcEndpoint) endpoint).getName();
58+
}
59+
return super.getPath(endpoint);
60+
}
61+
62+
@Override
63+
protected HandlerExecutionChain getHandlerExecutionChain(Object handler,
64+
HttpServletRequest request) {
65+
HandlerExecutionChain chain = super.getHandlerExecutionChain(handler, request);
66+
HandlerInterceptor[] interceptors = addSecurityInterceptor(
67+
chain.getInterceptors());
68+
return new HandlerExecutionChain(chain.getHandler(), interceptors);
69+
}
70+
71+
private HandlerInterceptor[] addSecurityInterceptor(HandlerInterceptor[] existing) {
72+
List<HandlerInterceptor> interceptors = new ArrayList<HandlerInterceptor>();
73+
interceptors.add(new SecurityInterceptor());
74+
if (existing != null) {
75+
interceptors.addAll(Arrays.asList(existing));
76+
}
77+
return interceptors.toArray(new HandlerInterceptor[interceptors.size()]);
78+
}
79+
80+
/**
81+
* Security interceptor to check cloud foundry token.
82+
*/
83+
static class SecurityInterceptor extends HandlerInterceptorAdapter {
84+
85+
@Override
86+
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
87+
Object handler) throws Exception {
88+
// Currently open
89+
return true;
90+
}
91+
92+
}
93+
}

spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMapping.java

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -209,29 +209,9 @@ public boolean isDisabled() {
209209
/**
210210
* Return the endpoints.
211211
* @return the endpoints
212-
* @see #getEndpoints(Class)
213212
*/
214-
public Set<? extends MvcEndpoint> getEndpoints() {
215-
return getEndpoints(MvcEndpoint.class);
216-
}
217-
218-
/**
219-
* Return the endpoints of the specified type.
220-
* @param <E> the endpoint type
221-
* @param type the endpoint type
222-
* @return the endpoints
223-
* @see #getEndpoints()
224-
* @since 1.5.0
225-
*/
226-
@SuppressWarnings("unchecked")
227-
public <E extends MvcEndpoint> Set<E> getEndpoints(Class<E> type) {
228-
Set<E> result = new HashSet<E>(this.endpoints.size());
229-
for (MvcEndpoint candidate : this.endpoints) {
230-
if (type.isInstance(candidate)) {
231-
result.add((E) candidate);
232-
}
233-
}
234-
return Collections.unmodifiableSet(result);
213+
public Set<MvcEndpoint> getEndpoints() {
214+
return Collections.unmodifiableSet(this.endpoints);
235215
}
236216

237217
@Override

spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpoints.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.boot.actuate.endpoint.mvc;
1818

1919
import java.util.Collection;
20+
import java.util.Collections;
2021
import java.util.HashSet;
2122
import java.util.Set;
2223

@@ -92,6 +93,22 @@ public Set<? extends MvcEndpoint> getEndpoints() {
9293
return this.endpoints;
9394
}
9495

96+
/**
97+
* Return the endpoints of the specified type.
98+
* @param <E> the Class type of the endpoints to be returned
99+
* @param type the endpoint type
100+
* @return the endpoints
101+
*/
102+
public <E extends MvcEndpoint> Set<E> getEndpoints(Class<E> type) {
103+
Set<E> result = new HashSet<E>(this.endpoints.size());
104+
for (MvcEndpoint candidate: this.endpoints) {
105+
if (type.isInstance(candidate)) {
106+
result.add((E) candidate);
107+
}
108+
}
109+
return Collections.unmodifiableSet(result);
110+
}
111+
95112
private boolean isGenericEndpoint(Class<?> type) {
96113
return !this.customTypes.contains(type)
97114
&& !MvcEndpoint.class.isAssignableFrom(type);

spring-boot-actuator/src/main/resources/META-INF/spring.factories

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ org.springframework.boot.actuate.autoconfigure.MetricsChannelAutoConfiguration,\
1717
org.springframework.boot.actuate.autoconfigure.MetricExportAutoConfiguration,\
1818
org.springframework.boot.actuate.autoconfigure.PublicMetricsAutoConfiguration,\
1919
org.springframework.boot.actuate.autoconfigure.TraceRepositoryAutoConfiguration,\
20-
org.springframework.boot.actuate.autoconfigure.TraceWebFilterAutoConfiguration
20+
org.springframework.boot.actuate.autoconfigure.TraceWebFilterAutoConfiguration,\
21+
org.springframework.boot.actuate.cloudfoundry.CloudFoundryActuatorAutoConfiguration
2122

2223
org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration=\
2324
org.springframework.boot.actuate.autoconfigure.EndpointWebMvcManagementContextConfiguration,\
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright 2012-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.cloudfoundry;
18+
19+
import org.junit.After;
20+
import org.junit.Before;
21+
import org.junit.Test;
22+
23+
import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration;
24+
import org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration;
25+
import org.springframework.boot.actuate.autoconfigure.EndpointWebMvcManagementContextConfiguration;
26+
import org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration;
27+
import org.springframework.boot.actuate.autoconfigure.ManagementWebSecurityAutoConfiguration;
28+
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
29+
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
30+
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
31+
import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration;
32+
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
33+
import org.springframework.boot.test.util.EnvironmentTestUtils;
34+
import org.springframework.mock.web.MockServletContext;
35+
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
36+
37+
import static org.assertj.core.api.Assertions.assertThat;
38+
39+
/**
40+
* Tests for {@link CloudFoundryActuatorAutoConfiguration}.
41+
*
42+
* @author Madhura Bhave
43+
*/
44+
public class CloudFoundryActuatorAutoConfigurationTests {
45+
46+
private AnnotationConfigWebApplicationContext context;
47+
48+
@Before
49+
public void setUp() {
50+
this.context = new AnnotationConfigWebApplicationContext();
51+
this.context.setServletContext(new MockServletContext());
52+
this.context.register(SecurityAutoConfiguration.class,
53+
WebMvcAutoConfiguration.class,
54+
ManagementWebSecurityAutoConfiguration.class,
55+
JacksonAutoConfiguration.class,
56+
HttpMessageConvertersAutoConfiguration.class,
57+
EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class,
58+
ManagementServerPropertiesAutoConfiguration.class,
59+
PropertyPlaceholderAutoConfiguration.class,
60+
EndpointWebMvcManagementContextConfiguration.class,
61+
CloudFoundryActuatorAutoConfiguration.class);
62+
}
63+
64+
@After
65+
public void close() {
66+
if (this.context != null) {
67+
this.context.close();
68+
}
69+
}
70+
71+
@Test
72+
public void cloudFoundryPlatformActive() throws Exception {
73+
EnvironmentTestUtils.addEnvironment(this.context, "VCAP_APPLICATION:---",
74+
"management.cloudfoundry.enabled:true");
75+
this.context.refresh();
76+
CloudFoundryEndpointHandlerMapping handlerMapping = this.context.getBean(
77+
"cloudFoundryEndpointHandlerMapping",
78+
CloudFoundryEndpointHandlerMapping.class);
79+
assertThat(handlerMapping.getPrefix()).isEqualTo("/cloudfoundryapplication");
80+
}
81+
82+
@Test
83+
public void cloudFoundryPlatformInactive() throws Exception {
84+
this.context.refresh();
85+
assertThat(this.context.containsBean("cloudFoundryEndpointHandlerMapping"))
86+
.isFalse();
87+
}
88+
89+
@Test
90+
public void cloudFoundryManagementEndpointsDisabled() throws Exception {
91+
EnvironmentTestUtils.addEnvironment(this.context, "VCAP_APPLICATION=---",
92+
"management.cloudfoundry.enabled:false");
93+
this.context.refresh();
94+
assertThat(this.context.containsBean("cloudFoundryEndpointHandlerMapping"))
95+
.isFalse();
96+
}
97+
98+
}

0 commit comments

Comments
 (0)