diff --git a/example/pom.xml b/example/pom.xml
new file mode 100644
index 00000000..b782b635
--- /dev/null
+++ b/example/pom.xml
@@ -0,0 +1,58 @@
+
+
+
+ spring-cloud-kubernetes-project
+ io.fabric8
+ 0.1-SNAPSHOT
+
+ 4.0.0
+
+ example
+
+
+
+
+ io.fabric8
+ spring-cloud-kubernetes-core
+ ${project.version}
+
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+ 1.4.1.RELEASE
+
+
+ org.hibernate
+ hibernate-validator
+ 5.2.4.Final
+
+
+
+
+
+
+
+ io.fabric8
+ fabric8-maven-plugin
+ 3.1.69
+
+
+
+ resource
+ build
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/example/src/main/java/io/app/App.java b/example/src/main/java/io/app/App.java
new file mode 100644
index 00000000..306d88aa
--- /dev/null
+++ b/example/src/main/java/io/app/App.java
@@ -0,0 +1,16 @@
+package io.app;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ *
+ */
+@SpringBootApplication
+public class App {
+
+ public static void main(String[] args) {
+ SpringApplication.run(App.class, args);
+ }
+
+}
diff --git a/example/src/main/java/io/app/MyBean.java b/example/src/main/java/io/app/MyBean.java
new file mode 100644
index 00000000..76569e3d
--- /dev/null
+++ b/example/src/main/java/io/app/MyBean.java
@@ -0,0 +1,29 @@
+package io.app;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+/**
+ *
+ */
+@Component
+@ConfigurationProperties(prefix = "bean")
+public class MyBean {
+
+ private String message;
+
+ @Scheduled(fixedDelay = 5000)
+ public void hello() {
+ System.out.println("The message is: " + message);
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+}
diff --git a/example/src/main/resources/application.properties b/example/src/main/resources/application.properties
new file mode 100644
index 00000000..358fdac4
--- /dev/null
+++ b/example/src/main/resources/application.properties
@@ -0,0 +1,9 @@
+spring.application.name=example
+
+
+#spring.cloud.kubernetes.reload.strategy=refresh
+#spring.cloud.kubernetes.reload.mode=polling
+#spring.cloud.kubernetes.reload.period=2000
+#spring.cloud.kubernetes.reload.monitoring-config-maps=false
+#spring.cloud.kubernetes.reload.monitoring-secrets=true
+#spring.cloud.kubernetes.reload.enabled=true
diff --git a/pom.xml b/pom.xml
index a560c839..14e1fdf3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -106,6 +106,7 @@
spring-cloud-starter-kubernetes-netflix
spring-cloud-starter-kubernetes-zipkin
spring-cloud-starter-kubernetes-all
+ example
diff --git a/readme.md b/readme.md
index d26eee16..58f20cda 100644
--- a/readme.md
+++ b/readme.md
@@ -64,7 +64,7 @@ The `ConfigMap` `PropertySource` when enabled will lookup Kubernetes for a `Conf
- apply individual configuration properties.
- apply as yaml the content of any property named `application.yaml`
-- apple as properties file the content of any property named `application.properties`
+- apply as properties file the content of any property named `application.properties`
Example:
@@ -196,6 +196,27 @@ You can select the Secrets to consume in a number of ways:
- The property spring.cloud.kubernetes.secrets.paths behave as defined by [Collection-based binding](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-Configuration-Binding#collection-based-binding).
- Access to secrets via API may be restricted for security reasons, the preferred way is to mount secret to the POD.
+#### PropertySource Reload
+
+Some applications may need to detect changes on external property sources and update their internal status to reflect the new configuration.
+The reload feature of Spring Cloud Kubernetes is able to trigger an application reload when its related ConfigMap or Secret change.
+
+This feature is disabled by default and can be enabled using the configuration property `spring.cloud.kubernetes.reload.enabled=true`
+ (eg. in the *application.properties* file).
+
+The following levels of reload are supported (property `spring.cloud.kubernetes.reload.strategy`):
+- **refresh (default)**: only spring beans annotated with `@ConfigurationProperties` or `@RefreshScope` are reloaded.
+This reload level leverages the refresh feature of Spring Cloud Context.
+- **restart_context**: the whole Spring _ApplicationContext_ is gracefully restarted. Beans are recreated with the new configuration.
+- **shutdown**: the Spring _ApplicationContext_ is shut down to activate a restart of the container.
+ When using this level, make sure that the lifecycle of all non-daemon threads is bound to the ApplicationContext
+ and that a replication controller or replica set is configured to restart the pod.
+
+Example:
+
+Assuming that the reload feature is enabled with default settings, the following bean will be refreshed
+
+
### Pod Health Indicator
Spring Boot uses [HealthIndicator](https://github.com/spring-projects/spring-boot/blob/master/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicator.java) to expose info about the health of an application.
diff --git a/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/config/ConfigMapPropertySourceLocator.java b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/config/ConfigMapPropertySourceLocator.java
index 98e6579e..7322a3df 100644
--- a/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/config/ConfigMapPropertySourceLocator.java
+++ b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/config/ConfigMapPropertySourceLocator.java
@@ -22,6 +22,7 @@ import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
+import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
@Order(0)
@@ -35,7 +36,7 @@ public class ConfigMapPropertySourceLocator implements PropertySourceLocator {
}
@Override
- public PropertySource> locate(Environment environment) {
+ public MapPropertySource locate(Environment environment) {
if (environment instanceof ConfigurableEnvironment) {
ConfigurableEnvironment env = (ConfigurableEnvironment) environment;
String appName = env.getProperty(Constants.SPRING_APPLICATION_NAME, Constants.FALLBACK_APPLICATION_NAME);
diff --git a/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/config/SecretsPropertySourceLocator.java b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/config/SecretsPropertySourceLocator.java
index 6da9d449..1b36e1bd 100644
--- a/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/config/SecretsPropertySourceLocator.java
+++ b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/config/SecretsPropertySourceLocator.java
@@ -22,6 +22,7 @@ import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
+import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
@Order(1)
@@ -35,7 +36,7 @@ public class SecretsPropertySourceLocator implements PropertySourceLocator {
}
@Override
- public PropertySource> locate(Environment environment) {
+ public MapPropertySource locate(Environment environment) {
return environment instanceof ConfigurableEnvironment
? new SecretsPropertySource(client, environment, properties)
: null;
diff --git a/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/reload/ConfigReloadAutoConfiguration.java b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/reload/ConfigReloadAutoConfiguration.java
new file mode 100644
index 00000000..bdec7c62
--- /dev/null
+++ b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/reload/ConfigReloadAutoConfiguration.java
@@ -0,0 +1,137 @@
+package io.fabric8.spring.cloud.kubernetes.reload;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+import javax.annotation.PreDestroy;
+
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.Watch;
+import io.fabric8.spring.cloud.kubernetes.config.ConfigMapPropertySourceLocator;
+import io.fabric8.spring.cloud.kubernetes.config.SecretsPropertySourceLocator;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.cloud.context.refresh.ContextRefresher;
+import org.springframework.cloud.context.restart.RestartEndpoint;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.env.AbstractEnvironment;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.stereotype.Component;
+
+/**
+ * Definition of beans needed for the automatic reload of configuration.
+ */
+@Configuration
+@ConditionalOnProperty(value = "spring.cloud.kubernetes.enabled", matchIfMissing = true)
+@EnableConfigurationProperties(ConfigReloadProperties.class)
+public class ConfigReloadAutoConfiguration {
+
+ /**
+ * Configuration reload must be enabled explicitly.
+ */
+ @ConditionalOnProperty(value = "spring.cloud.kubernetes.reload.enabled")
+ @ConditionalOnClass({RestartEndpoint.class, ContextRefresher.class})
+ @EnableScheduling
+ @EnableAsync
+ protected static class ConfigReloadAutoConfigurationBeans {
+
+ @Autowired
+ private AbstractEnvironment environment;
+
+ @Autowired
+ private KubernetesClient kubernetesClient;
+
+ @Autowired
+ private ConfigMapPropertySourceLocator configMapPropertySourceLocator;
+
+ @Autowired
+ private SecretsPropertySourceLocator secretsPropertySourceLocator;
+
+ /**
+ * Provides a bean that listen to configuration changes and fire a reload.
+ */
+ @Bean
+ @ConditionalOnMissingBean
+ public ConfigurationChangeDetector propertyChangeWatcher(ConfigReloadProperties properties, ConfigurationUpdateStrategy strategy, EventWatcher eventWatcher) {
+ switch (properties.getMode()) {
+ case POLLING:
+ return new PollingConfigurationChangeDetector(environment, properties, kubernetesClient, strategy, configMapPropertySourceLocator, secretsPropertySourceLocator);
+ case EVENT:
+ return new EventBasedConfigurationChangeDetector(environment, properties, kubernetesClient, strategy, configMapPropertySourceLocator, secretsPropertySourceLocator, eventWatcher);
+ }
+ throw new IllegalStateException("Unsupported configuration reload mode: " + properties.getMode());
+ }
+
+ /**
+ * Provides the action to execute when the configuration changes.
+ */
+ @Bean
+ @ConditionalOnMissingBean
+ public ConfigurationUpdateStrategy configurationUpdateStrategy(ConfigReloadProperties properties, ConfigurableApplicationContext ctx, RestartEndpoint restarter, ContextRefresher refresher) {
+ switch (properties.getStrategy()) {
+ case RESTART_CONTEXT:
+ return new ConfigurationUpdateStrategy(properties.getStrategy().name(), restarter::restart);
+ case REFRESH:
+ return new ConfigurationUpdateStrategy(properties.getStrategy().name(), refresher::refresh);
+ case SHUTDOWN:
+ return new ConfigurationUpdateStrategy(properties.getStrategy().name(), ctx::close);
+ }
+ throw new IllegalStateException("Unsupported configuration update strategy: " + properties.getStrategy());
+ }
+
+
+ /**
+ * Manages watches asynchronously and clean them up on context close.
+ */
+ @Component
+ public static class DefaultEventWatcher implements EventWatcher {
+ private Logger log = LoggerFactory.getLogger(getClass());
+
+ private KubernetesClient kubernetesClient;
+
+ private Map watches;
+
+ @Autowired
+ public DefaultEventWatcher(KubernetesClient kubernetesClient) {
+ this.kubernetesClient = kubernetesClient;
+ this.watches = new ConcurrentHashMap<>();
+ }
+
+ @Async
+ public void addWatch(String name, Function watch) {
+ if (watches.containsKey(name)) {
+ throw new IllegalArgumentException("Watch already present: " + name);
+ }
+
+ watches.put(name, watch.apply(kubernetesClient));
+ log.info("Added new Kubernetes watch: {}", name);
+ }
+
+ @PreDestroy
+ public void unwatch() {
+ if (this.watches != null) {
+ for (Watch watch : this.watches.values()) {
+ try {
+ watch.close();
+
+ } catch (Exception e) {
+ log.error("Error while closing the watch connection", e);
+ }
+ }
+ }
+ }
+
+ }
+ }
+
+}
diff --git a/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/reload/ConfigReloadProperties.java b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/reload/ConfigReloadProperties.java
new file mode 100644
index 00000000..c5c4c551
--- /dev/null
+++ b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/reload/ConfigReloadProperties.java
@@ -0,0 +1,124 @@
+package io.fabric8.spring.cloud.kubernetes.reload;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * General configuration for the configuration reload.
+ */
+@ConfigurationProperties(prefix = "spring.cloud.kubernetes.reload")
+public class ConfigReloadProperties {
+
+ /**
+ * Enables the Kubernetes configuration reload on change.
+ */
+ private boolean enabled = false;
+
+ /**
+ * Enables monitoring on config maps to detect changes.
+ */
+ private boolean monitoringConfigMaps = true;
+
+ /**
+ * Enables monitoring on secrets to detect changes.
+ */
+ private boolean monitoringSecrets = false;
+
+ /**
+ * Sets the reload strategy for Kubernetes configuration reload on change.
+ */
+ private ReloadStrategy strategy = ReloadStrategy.REFRESH;
+
+ /**
+ * Sets the detection mode for Kubernetes configuration reload.
+ */
+ private ReloadDetectionMode mode = ReloadDetectionMode.EVENT;
+
+ /**
+ * Sets the polling period in milliseconds to use when the detection mode is POLLING.
+ */
+ private Long period = 15000L;
+
+ public ConfigReloadProperties() {
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public boolean isMonitoringConfigMaps() {
+ return monitoringConfigMaps;
+ }
+
+ public void setMonitoringConfigMaps(boolean monitoringConfigMaps) {
+ this.monitoringConfigMaps = monitoringConfigMaps;
+ }
+
+ public boolean isMonitoringSecrets() {
+ return monitoringSecrets;
+ }
+
+ public void setMonitoringSecrets(boolean monitoringSecrets) {
+ this.monitoringSecrets = monitoringSecrets;
+ }
+
+ public ReloadStrategy getStrategy() {
+ return strategy;
+ }
+
+ public void setStrategy(ReloadStrategy strategy) {
+ this.strategy = strategy;
+ }
+
+ public ReloadDetectionMode getMode() {
+ return mode;
+ }
+
+ public Long getPeriod() {
+ return period;
+ }
+
+ public void setPeriod(Long period) {
+ this.period = period;
+ }
+
+ public void setMode(ReloadDetectionMode mode) {
+ this.mode = mode;
+ }
+
+ public enum ReloadStrategy {
+ /**
+ * Fire a refresh of beans annotated with @ConfigurationProperties or @RefreshScope.
+ */
+ REFRESH,
+
+ /**
+ * Restarts the Spring ApplicationContext to apply the new configuration.
+ */
+ RESTART_CONTEXT,
+
+ /**
+ * Shuts down the Spring ApplicationContext to activate a restart of the container.
+ * Make sure that the lifecycle of all non-daemon threads is bound to the ApplicationContext and that
+ * a replication controller or replica set is configured to restart the pod.
+ */
+ SHUTDOWN
+ }
+
+ public enum ReloadDetectionMode {
+ /**
+ * Enables a polling task that retrieves periodically all external properties and
+ * fire a reload when they change.
+ */
+ POLLING,
+
+ /**
+ * Listens to Kubernetes events and checks if a reload is needed when configmaps or secrets change.
+ */
+ EVENT
+ }
+
+}
diff --git a/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/reload/ConfigurationChangeDetector.java b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/reload/ConfigurationChangeDetector.java
new file mode 100644
index 00000000..b0b2169e
--- /dev/null
+++ b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/reload/ConfigurationChangeDetector.java
@@ -0,0 +1,109 @@
+package io.fabric8.spring.cloud.kubernetes.reload;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.PreDestroy;
+
+import io.fabric8.kubernetes.client.Client;
+import io.fabric8.kubernetes.client.KubernetesClient;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.env.CompositePropertySource;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.MapPropertySource;
+import org.springframework.core.env.PropertySource;
+
+/**
+ * This is the superclass of all beans that can listen to changes in the configuration and fire a reload.
+ */
+public abstract class ConfigurationChangeDetector {
+
+ protected Logger log = LoggerFactory.getLogger(getClass());
+
+ protected ConfigurableEnvironment environment;
+
+ protected ConfigReloadProperties properties;
+
+ protected KubernetesClient kubernetesClient;
+
+ protected ConfigurationUpdateStrategy strategy;
+
+ public ConfigurationChangeDetector(ConfigurableEnvironment environment, ConfigReloadProperties properties, KubernetesClient kubernetesClient, ConfigurationUpdateStrategy strategy) {
+ this.environment = environment;
+ this.properties = properties;
+ this.kubernetesClient = kubernetesClient;
+ this.strategy = strategy;
+ }
+
+ @PreDestroy
+ public void shutdown() {
+ // Ensure the kubernetes client is cleaned up from spare threads when shutting down
+ if (kubernetesClient instanceof Client) {
+ ((Client) kubernetesClient).close();
+ }
+ }
+
+ public void reloadProperties() {
+ log.info("Reloading using strategy: " + strategy.getName());
+ strategy.reload();
+ }
+
+ /**
+ * Determines if two property sources are different.
+ */
+ protected boolean changed(MapPropertySource mp1, MapPropertySource mp2) {
+ if (mp1 == mp2) return false;
+ if (mp1 == null && mp2 != null || mp1 != null && mp2 == null) return true;
+
+ Map s1 = mp1.getSource();
+ Map s2 = mp2.getSource();
+
+ return s1 == null ? s2 != null : !s1.equals(s2);
+ }
+
+ /**
+ * Finds one registered property source of the given type, logging a warning if
+ * multiple property sources of that type are available.
+ */
+ protected > S findPropertySource(Class sourceClass) {
+ List sources = findPropertySources(sourceClass);
+ if (sources.size() == 0) {
+ return null;
+ }
+ if (sources.size() > 1) {
+ log.warn("Found more than one property source of type " + sourceClass);
+ }
+ return sources.get(0);
+ }
+
+ /**
+ * Finds all registered property sources of the given type.
+ */
+ protected > List findPropertySources(Class sourceClass) {
+ List managedSources = new LinkedList<>();
+
+ LinkedList> sources = toLinkedList(environment.getPropertySources());
+ while (!sources.isEmpty()) {
+ PropertySource> source = sources.pop();
+ if (source instanceof CompositePropertySource) {
+ CompositePropertySource comp = (CompositePropertySource) source;
+ sources.addAll(comp.getPropertySources());
+ } else if (sourceClass.isInstance(source)) {
+ managedSources.add(sourceClass.cast(source));
+ }
+ }
+
+ return managedSources;
+ }
+
+ private LinkedList toLinkedList(Iterable it) {
+ LinkedList list = new LinkedList();
+ for (E e : it) {
+ list.add(e);
+ }
+ return list;
+ }
+
+}
diff --git a/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/reload/ConfigurationUpdateStrategy.java b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/reload/ConfigurationUpdateStrategy.java
new file mode 100644
index 00000000..008ed792
--- /dev/null
+++ b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/reload/ConfigurationUpdateStrategy.java
@@ -0,0 +1,36 @@
+package io.fabric8.spring.cloud.kubernetes.reload;
+
+import java.util.Objects;
+
+/**
+ * This is the superclass of all named strategies that can be fired when the configuration changes.
+ */
+public class ConfigurationUpdateStrategy {
+
+ private String name;
+
+ private Runnable reloadProcedure;
+
+ public ConfigurationUpdateStrategy(String name, Runnable reloadProcedure) {
+ Objects.requireNonNull(name, "name cannot be null");
+ Objects.requireNonNull(reloadProcedure, "reloadProcedure cannot be null");
+ this.name = name;
+ this.reloadProcedure = reloadProcedure;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void reload() {
+ this.reloadProcedure.run();
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("ConfigurationUpdateStrategy{");
+ sb.append("name='").append(name).append('\'');
+ sb.append('}');
+ return sb.toString();
+ }
+}
diff --git a/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/reload/EventBasedConfigurationChangeDetector.java b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/reload/EventBasedConfigurationChangeDetector.java
new file mode 100644
index 00000000..1832d7f6
--- /dev/null
+++ b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/reload/EventBasedConfigurationChangeDetector.java
@@ -0,0 +1,100 @@
+package io.fabric8.spring.cloud.kubernetes.reload;
+
+import javax.annotation.PostConstruct;
+
+import io.fabric8.kubernetes.api.model.ConfigMap;
+import io.fabric8.kubernetes.api.model.Secret;
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.KubernetesClientException;
+import io.fabric8.kubernetes.client.Watcher;
+import io.fabric8.spring.cloud.kubernetes.config.ConfigMapPropertySource;
+import io.fabric8.spring.cloud.kubernetes.config.ConfigMapPropertySourceLocator;
+import io.fabric8.spring.cloud.kubernetes.config.SecretsPropertySource;
+import io.fabric8.spring.cloud.kubernetes.config.SecretsPropertySourceLocator;
+
+import org.springframework.core.env.AbstractEnvironment;
+import org.springframework.core.env.MapPropertySource;
+
+/**
+ * A change detector that subscribes to changes in secrets and configmaps and fire a reload when something changes.
+ */
+public class EventBasedConfigurationChangeDetector extends ConfigurationChangeDetector {
+
+ private ConfigMapPropertySourceLocator configMapPropertySourceLocator;
+
+ private SecretsPropertySourceLocator secretsPropertySourceLocator;
+
+ private EventWatcher eventWatcher;
+
+ public EventBasedConfigurationChangeDetector(AbstractEnvironment environment,
+ ConfigReloadProperties properties,
+ KubernetesClient kubernetesClient,
+ ConfigurationUpdateStrategy strategy,
+ ConfigMapPropertySourceLocator configMapPropertySourceLocator,
+ SecretsPropertySourceLocator secretsPropertySourceLocator,
+ EventWatcher eventWatcher) {
+ super(environment, properties, kubernetesClient, strategy);
+
+ this.eventWatcher = eventWatcher;
+ this.configMapPropertySourceLocator = configMapPropertySourceLocator;
+ this.secretsPropertySourceLocator = secretsPropertySourceLocator;
+ }
+
+ @PostConstruct
+ public void watch() {
+
+ if (properties.isMonitoringConfigMaps()) {
+ eventWatcher.addWatch("config-maps-watch", k -> k.configMaps()
+ .watch(new Watcher() {
+ @Override
+ public void eventReceived(Action action, ConfigMap configMap) {
+ onEvent(configMap);
+ }
+
+ @Override
+ public void onClose(KubernetesClientException e) {
+ }
+ }));
+ }
+
+ if (properties.isMonitoringSecrets()) {
+ eventWatcher.addWatch("secrets-watch", k -> k.secrets()
+ .watch(new Watcher() {
+ @Override
+ public void eventReceived(Action action, Secret secret) {
+ onEvent(secret);
+ }
+
+ @Override
+ public void onClose(KubernetesClientException e) {
+ }
+ }));
+ }
+
+ log.info("Kubernetes polling configuration change detector activated");
+ }
+
+ private void onEvent(ConfigMap configMap) {
+ MapPropertySource currentConfigMapSource = findPropertySource(ConfigMapPropertySource.class);
+ if (currentConfigMapSource != null) {
+ MapPropertySource newConfigMapSource = configMapPropertySourceLocator.locate(environment);
+ if (changed(currentConfigMapSource, newConfigMapSource)) {
+ log.info("Detected change in config maps");
+ reloadProperties();
+ }
+ }
+ }
+
+ private void onEvent(Secret secret) {
+ MapPropertySource currentSecretSource = findPropertySource(SecretsPropertySource.class);
+ if (currentSecretSource != null) {
+ MapPropertySource newSecretSource = secretsPropertySourceLocator.locate(environment);
+ if (changed(currentSecretSource, newSecretSource)) {
+ log.info("Detected change in secrets");
+ reloadProperties();
+ }
+ }
+ }
+
+
+}
diff --git a/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/reload/EventWatcher.java b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/reload/EventWatcher.java
new file mode 100644
index 00000000..24f5061e
--- /dev/null
+++ b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/reload/EventWatcher.java
@@ -0,0 +1,15 @@
+package io.fabric8.spring.cloud.kubernetes.reload;
+
+import java.util.function.Function;
+
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.Watch;
+
+/**
+ * Provides a way to start Kubernetes watches and bind their lifecycle to the application context.
+ */
+public interface EventWatcher {
+
+ void addWatch(String name, Function watch);
+
+}
diff --git a/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/reload/PollingConfigurationChangeDetector.java b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/reload/PollingConfigurationChangeDetector.java
new file mode 100644
index 00000000..9e2f5bdc
--- /dev/null
+++ b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/reload/PollingConfigurationChangeDetector.java
@@ -0,0 +1,67 @@
+package io.fabric8.spring.cloud.kubernetes.reload;
+
+import javax.annotation.PostConstruct;
+
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.spring.cloud.kubernetes.config.ConfigMapPropertySource;
+import io.fabric8.spring.cloud.kubernetes.config.ConfigMapPropertySourceLocator;
+import io.fabric8.spring.cloud.kubernetes.config.SecretsPropertySource;
+import io.fabric8.spring.cloud.kubernetes.config.SecretsPropertySourceLocator;
+
+import org.springframework.core.env.AbstractEnvironment;
+import org.springframework.core.env.MapPropertySource;
+import org.springframework.scheduling.annotation.Scheduled;
+
+/**
+ * A change detector that periodically retrieves secrets and configmaps and fire a reload when something changes.
+ */
+public class PollingConfigurationChangeDetector extends ConfigurationChangeDetector {
+
+ private ConfigMapPropertySourceLocator configMapPropertySourceLocator;
+
+ private SecretsPropertySourceLocator secretsPropertySourceLocator;
+
+ public PollingConfigurationChangeDetector(AbstractEnvironment environment,
+ ConfigReloadProperties properties,
+ KubernetesClient kubernetesClient,
+ ConfigurationUpdateStrategy strategy,
+ ConfigMapPropertySourceLocator configMapPropertySourceLocator,
+ SecretsPropertySourceLocator secretsPropertySourceLocator) {
+ super(environment, properties, kubernetesClient, strategy);
+
+ this.configMapPropertySourceLocator = configMapPropertySourceLocator;
+ this.secretsPropertySourceLocator = secretsPropertySourceLocator;
+ }
+
+ @PostConstruct
+ public void init() {
+ log.info("Kubernetes polling configuration change detector activated");
+ }
+
+ @Scheduled(initialDelayString = "${spring.cloud.kubernetes.reload.period:15000}", fixedDelayString = "${spring.cloud.kubernetes.reload.period:15000}")
+ public void executeCycle() {
+
+ boolean changedConfigMap = false;
+ if (properties.isMonitoringConfigMaps()) {
+ MapPropertySource currentConfigMapSource = findPropertySource(ConfigMapPropertySource.class);
+ if (currentConfigMapSource != null) {
+ MapPropertySource newConfigMapSource = configMapPropertySourceLocator.locate(environment);
+ changedConfigMap = changed(currentConfigMapSource, newConfigMapSource);
+ }
+ }
+
+ boolean changedSecrets = false;
+ if (properties.isMonitoringSecrets()) {
+ MapPropertySource currentSecretSource = findPropertySource(SecretsPropertySource.class);
+ if (currentSecretSource != null) {
+ MapPropertySource newSecretSource = secretsPropertySourceLocator.locate(environment);
+ changedSecrets = changed(currentSecretSource, newSecretSource);
+ }
+ }
+
+ if (changedConfigMap || changedSecrets) {
+ reloadProperties();
+ }
+ }
+
+}
diff --git a/spring-cloud-kubernetes-core/src/main/resources/META-INF/spring.factories b/spring-cloud-kubernetes-core/src/main/resources/META-INF/spring.factories
index 9db489c5..b1ba6a70 100644
--- a/spring-cloud-kubernetes-core/src/main/resources/META-INF/spring.factories
+++ b/spring-cloud-kubernetes-core/src/main/resources/META-INF/spring.factories
@@ -1,5 +1,6 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
-io.fabric8.spring.cloud.kubernetes.KubernetesAutoConfiguration
+io.fabric8.spring.cloud.kubernetes.KubernetesAutoConfiguration,\
+io.fabric8.spring.cloud.kubernetes.reload.ConfigReloadAutoConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
io.fabric8.spring.cloud.kubernetes.config.BootstrapConfiguration