Features actuator endpoint
Allows modules to declare features and display if they enabled or not.
This commit is contained in:
@@ -16,8 +16,12 @@
|
||||
|
||||
package org.springframework.cloud.client;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.endpoint.Endpoint;
|
||||
import org.springframework.boot.actuate.health.HealthAggregator;
|
||||
import org.springframework.boot.actuate.health.HealthIndicator;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
|
||||
@@ -25,10 +29,13 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cloud.client.actuator.FeaturesEndpoint;
|
||||
import org.springframework.cloud.client.actuator.HasFeatures;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.cloud.client.discovery.health.DiscoveryClientHealthIndicator;
|
||||
import org.springframework.cloud.client.discovery.health.DiscoveryCompositeHealthIndicator;
|
||||
import org.springframework.cloud.client.discovery.health.DiscoveryHealthIndicator;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@@ -56,4 +63,22 @@ public class CommonsClientAutoConfiguration {
|
||||
return new DiscoveryCompositeHealthIndicator(aggregator, indicators);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public HasFeatures commonsFeatures() {
|
||||
return HasFeatures.abstractFeatures(DiscoveryClient.class,
|
||||
LoadBalancerClient.class);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnClass(Endpoint.class)
|
||||
protected static class ActuatorConfiguration {
|
||||
@Autowired(required = false)
|
||||
private List<HasFeatures> hasFeatures = new ArrayList<>();
|
||||
|
||||
@Bean
|
||||
public FeaturesEndpoint featuresEndpoint() {
|
||||
return new FeaturesEndpoint(hasFeatures);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package org.springframework.cloud.client.actuator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.boot.actuate.endpoint.AbstractEndpoint;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "endpoints.features", ignoreUnknownFields = false)
|
||||
public class FeaturesEndpoint extends AbstractEndpoint<FeaturesEndpoint.Features> implements ApplicationContextAware {
|
||||
|
||||
private final List<HasFeatures> hasFeaturesList;
|
||||
private ApplicationContext context;
|
||||
|
||||
public FeaturesEndpoint(List<HasFeatures> hasFeaturesList) {
|
||||
super("features", false);
|
||||
this.hasFeaturesList = hasFeaturesList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext context) throws BeansException {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Features invoke() {
|
||||
Features features = new Features();
|
||||
|
||||
for (HasFeatures hasFeatures : hasFeaturesList) {
|
||||
List<Class> abstractFeatures = hasFeatures.getAbstractFeatures();
|
||||
if (abstractFeatures != null) {
|
||||
for (Class clazz : abstractFeatures) {
|
||||
addAbstractFeature(features, clazz);
|
||||
}
|
||||
}
|
||||
|
||||
List<NamedFeature> namedFeatures = hasFeatures.getNamedFeatures();
|
||||
if (namedFeatures != null) {
|
||||
for (NamedFeature namedFeature : namedFeatures) {
|
||||
addFeature(features, namedFeature);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return features;
|
||||
}
|
||||
|
||||
private void addAbstractFeature(Features features, Class type) {
|
||||
addFeature(features, new NamedFeature(type.getSimpleName(), type));
|
||||
}
|
||||
|
||||
private void addFeature(Features features, NamedFeature feature) {
|
||||
try {
|
||||
Object bean = context.getBean(feature.getType());
|
||||
Class<?> beanClass = bean.getClass();
|
||||
features.getEnabled().add(new Feature(feature.getName(),
|
||||
beanClass.getCanonicalName(),
|
||||
beanClass.getPackage().getImplementationVersion(),
|
||||
beanClass.getPackage().getImplementationVendor()));
|
||||
} catch (NoSuchBeanDefinitionException e) {
|
||||
features.getDisabled().add(feature.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Value
|
||||
class Features {
|
||||
List<Feature> enabled = new ArrayList<>();
|
||||
List<String> disabled = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Value
|
||||
class Feature {
|
||||
final String type;
|
||||
final String name;
|
||||
final String version;
|
||||
final String vendor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.springframework.cloud.client.actuator;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Singular;
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@Value
|
||||
@Builder
|
||||
public class HasFeatures {
|
||||
@Singular
|
||||
private final List<Class> abstractFeatures;
|
||||
@Singular
|
||||
private final List<NamedFeature> namedFeatures;
|
||||
|
||||
public static HasFeatures abstractFeatures(Class... abstractFeatures) {
|
||||
return new HasFeatures(Arrays.asList(abstractFeatures), Collections.<NamedFeature>emptyList());
|
||||
}
|
||||
|
||||
public static HasFeatures namedFeatures(NamedFeature... namedFeatures) {
|
||||
return new HasFeatures(Collections.<Class>emptyList(), Arrays.asList(namedFeatures));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.springframework.cloud.client.actuator;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@Value
|
||||
public class NamedFeature {
|
||||
private final String name;
|
||||
private final Class<?> type;
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package org.springframework.cloud.client.actuator;
|
||||
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public class FeaturesEndpointTests {
|
||||
|
||||
private AnnotationConfigApplicationContext context;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
this.context.register(JacksonAutoConfiguration.class, FeaturesConfig.class, Config.class);
|
||||
this.context.refresh();
|
||||
}
|
||||
|
||||
@After
|
||||
public void close() {
|
||||
if (this.context != null) {
|
||||
this.context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeWorks() {
|
||||
FeaturesEndpoint.Features features = this.context.getBean(FeaturesEndpoint.class).invoke();
|
||||
assertThat(features, is(notNullValue()));
|
||||
assertThat(features.getEnabled().size(), is(equalTo(2)));
|
||||
assertThat(features.getDisabled().size(), is(equalTo(1)));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class FeaturesConfig {
|
||||
@Bean
|
||||
Foo foo() {
|
||||
return new Foo();
|
||||
}
|
||||
|
||||
@Bean
|
||||
Bar bar() {
|
||||
return new Bar();
|
||||
}
|
||||
|
||||
@Bean
|
||||
HasFeatures localFeatures() {
|
||||
return HasFeatures.builder()
|
||||
.abstractFeature(Foo.class)
|
||||
.namedFeature(new NamedFeature("Bar Feature", Bar.class))
|
||||
.abstractFeature(Baz.class)
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties
|
||||
public static class Config {
|
||||
@Autowired(required = false)
|
||||
private List<HasFeatures> hasFeatures = new ArrayList<>();
|
||||
|
||||
@Bean
|
||||
public FeaturesEndpoint cloudEndpoint() {
|
||||
return new FeaturesEndpoint(hasFeatures);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Foo {}
|
||||
public static class Bar {}
|
||||
public static class Baz {}
|
||||
}
|
||||
Reference in New Issue
Block a user