diff --git a/README.md b/README.md
index b167402452..dca7e33794 100644
--- a/README.md
+++ b/README.md
@@ -102,6 +102,8 @@ We prepared a few examples for common use-cases which are shown below:
Build a controller reconciling the state of world by list-watching one or multiple resources.
- ([6.0.0+](https://github.com/kubernetes-client/java/tree/client-java-parent-6.0.0)) [LeaderElectionExample](https://github.com/kubernetes-client/java/blob/master/examples/src/main/java/io/kubernetes/client/examples/LeaderElectionExample.java):
Leader election utilities to help implement HA controllers.
+ - ([9.0.0+](https://github.com/kubernetes-client/java/tree/client-java-parent-9.0.0)) [SpringIntegrationControllerExample](https://github.com/kubernetes-client/java/blob/master/examples/src/main/java/io/kubernetes/client/examples/SpringControllerExample.java):
+ Building a kubernetes controller based on spring framework's bean injection.
__list all pods__:
diff --git a/examples/pom.xml b/examples/pom.xml
index e1b1245d4c..d80a160310 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -26,6 +26,11 @@
client-java-extended
${project.version}
+
+ io.kubernetes
+ client-java-spring-integration
+ ${project.version}
+
io.kubernetes
client-java-proto
diff --git a/examples/src/main/java/io/kubernetes/client/examples/SpringControllerExample.java b/examples/src/main/java/io/kubernetes/client/examples/SpringControllerExample.java
new file mode 100644
index 0000000000..b7bd6ce500
--- /dev/null
+++ b/examples/src/main/java/io/kubernetes/client/examples/SpringControllerExample.java
@@ -0,0 +1,137 @@
+package io.kubernetes.client.examples;
+
+import io.kubernetes.client.extended.controller.Controller;
+import io.kubernetes.client.extended.controller.reconciler.Reconciler;
+import io.kubernetes.client.extended.controller.reconciler.Request;
+import io.kubernetes.client.extended.controller.reconciler.Result;
+import io.kubernetes.client.informer.SharedInformer;
+import io.kubernetes.client.informer.SharedInformerFactory;
+import io.kubernetes.client.informer.cache.Lister;
+import io.kubernetes.client.openapi.ApiClient;
+import io.kubernetes.client.openapi.models.V1Node;
+import io.kubernetes.client.openapi.models.V1NodeList;
+import io.kubernetes.client.openapi.models.V1Pod;
+import io.kubernetes.client.openapi.models.V1PodList;
+import io.kubernetes.client.spring.extended.controller.annotation.*;
+import io.kubernetes.client.util.ClientBuilder;
+import java.io.IOException;
+import java.time.Duration;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+
+@SpringBootApplication
+public class SpringControllerExample {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringControllerExample.class, args);
+ }
+
+ @Configuration
+ @ComponentScan(
+ basePackages = "io.kubernetes.client.spring.extended.controller"
+ ) // Scanning beans under this package is *REQUIRED* for informers/reconciler injections.
+ public static class AppConfig {
+
+ @Bean
+ public CommandLineRunner commandLineRunner(
+ SharedInformerFactory sharedInformerFactory,
+ @Qualifier("node-printing-controller") Controller nodePrintingController) {
+ return args -> {
+ System.out.println("starting informers..");
+ sharedInformerFactory.startAllRegisteredInformers();
+
+ System.out.println("running controller..");
+ nodePrintingController.run();
+ };
+ }
+
+ // *OPTIONAL*
+ // Injecting and customize your ApiClient, if not specified, fallbacks to {@link
+ // io.kubernetes.client.util.ClientBuilder#standard}
+ @Bean
+ public ApiClient myApiClient() throws IOException {
+ ApiClient apiClient = ClientBuilder.standard().build();
+ return apiClient.setHttpClient(
+ apiClient.getHttpClient().newBuilder().readTimeout(Duration.ZERO).build());
+ }
+
+ // *REQUIRED*
+ // Injecting your SharedInformerFactory class annotated `@KubernetesInformers`
+ @Bean("sharedInformerFactory")
+ public SharedInformerFactory sharedInformerFactory() {
+ return new MySharedInformerFactory();
+ }
+
+ @Bean
+ public NodePrintingReconciler nodePrintingReconciler(
+ Lister podLister, Lister nodeLister, SharedInformer nodeInformer) {
+ return new NodePrintingReconciler(podLister, nodeLister, nodeInformer);
+ }
+ }
+
+ @KubernetesInformers({ // Defining what resources is the informer-factory actually watching.
+ @KubernetesInformer(
+ apiTypeClass = V1Node.class,
+ apiListTypeClass = V1NodeList.class,
+ groupVersionResource =
+ @GroupVersionResource(apiGroup = "", apiVersion = "v1", resourcePlural = "nodes"),
+ resyncPeriodMillis = 60 * 1000L
+ ),
+ @KubernetesInformer(
+ apiTypeClass = V1Pod.class,
+ apiListTypeClass = V1PodList.class,
+ groupVersionResource =
+ @GroupVersionResource(apiGroup = "", apiVersion = "v1", resourcePlural = "pods")
+ ),
+ })
+ public static class MySharedInformerFactory extends SharedInformerFactory {}
+
+ // As long as a reconciler bean attached `@KubernetesReconciler` detected in the context, we will
+ // be automatically creating a conresponding controller bean implementing {@link
+ // io.kubernetes.client.extended.controller.Controller}
+ // with the name specified and registering it to the spring bean-factory.
+ @KubernetesReconciler(
+ value = "node-printing-controller",
+ watches =
+ @KubernetesReconcilerWatches({
+ @KubernetesReconcilerWatch(
+ apiTypeClass = V1Node.class,
+ resyncPeriodMillis = 60 * 1000L // fully resync every 1 minute
+ ),
+ })
+ )
+ public static class NodePrintingReconciler implements Reconciler {
+
+ public NodePrintingReconciler(
+ Lister podLister, Lister nodeLister, SharedInformer nodeInformer) {
+ this.nodeLister = nodeLister;
+ this.podLister = podLister;
+ this.nodeInformer = nodeInformer;
+ }
+
+ private SharedInformer nodeInformer;
+
+ private Lister nodeLister;
+
+ private Lister podLister;
+
+ // *OPTIONAL*
+ // If you feed like hold the controller from running util some condition..
+ @KubernetesReconcilerReadyFunc
+ boolean informerReady() {
+ return nodeInformer.hasSynced();
+ }
+
+ @Override
+ public Result reconcile(Request request) {
+ V1Node node = nodeLister.get(request.getName());
+ System.out.println("triggered reconciling " + node.getMetadata().getName());
+ return new Result(false);
+ }
+ }
+}
diff --git a/spring/pom.xml b/spring/pom.xml
index a0bb2e4043..37443ad3f7 100644
--- a/spring/pom.xml
+++ b/spring/pom.xml
@@ -29,6 +29,11 @@
spring-boot
${spring.boot.version}
+
+ org.springframework.boot
+ spring-boot-autoconfigure
+ ${spring.boot.version}
+
junit
diff --git a/spring/src/main/java/io/kubernetes/client/spring/extended/controller/KubernetesInformerFactoryProcessor.java b/spring/src/main/java/io/kubernetes/client/spring/extended/controller/KubernetesInformerFactoryProcessor.java
index 59853bacde..368b769d3a 100644
--- a/spring/src/main/java/io/kubernetes/client/spring/extended/controller/KubernetesInformerFactoryProcessor.java
+++ b/spring/src/main/java/io/kubernetes/client/spring/extended/controller/KubernetesInformerFactoryProcessor.java
@@ -16,14 +16,18 @@
import io.kubernetes.client.util.ClientBuilder;
import io.kubernetes.client.util.Watchable;
import java.io.IOException;
+import java.time.Duration;
import java.util.Map;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
-import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
+import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.Ordered;
import org.springframework.core.ResolvableType;
import org.springframework.stereotype.Component;
@@ -37,13 +41,16 @@
* injects informers to spring context with the underlying constructing process hidden from users.
*/
@Component
-public class KubernetesInformerFactoryProcessor implements BeanFactoryPostProcessor, Ordered {
+public class KubernetesInformerFactoryProcessor
+ implements BeanDefinitionRegistryPostProcessor, Ordered {
private static final Logger log =
LoggerFactory.getLogger(KubernetesInformerFactoryProcessor.class);
public static final int ORDER = 0;
+ private BeanDefinitionRegistry beanDefinitionRegistry;
+
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
@@ -79,6 +86,8 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
return;
}
}
+ apiClient.setHttpClient(
+ apiClient.getHttpClient().newBuilder().readTimeout(Duration.ZERO).build());
SharedInformerFactory sharedInformerFactory = beanFactory.getBean(SharedInformerFactory.class);
KubernetesInformers kubernetesInformers =
@@ -124,12 +133,24 @@ public Watchable watch(CallGeneratorParams params) throws ApiException {
ResolvableType informerType =
ResolvableType.forClassWithGenerics(
SharedInformer.class, kubernetesInformer.apiTypeClass());
- beanFactory.registerResolvableDependency(informerType.resolve(), sharedIndexInformer);
+ RootBeanDefinition informerBean = new RootBeanDefinition();
+ informerBean.setTargetType(informerType);
+ informerBean.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
+ informerBean.setAutowireCandidate(true);
+ String informerBeanName = informerType.toString();
+ this.beanDefinitionRegistry.registerBeanDefinition(informerBeanName, informerBean);
+ beanFactory.registerSingleton(informerBeanName, sharedIndexInformer);
Lister lister = new Lister(sharedIndexInformer.getIndexer());
ResolvableType listerType =
ResolvableType.forClassWithGenerics(Lister.class, kubernetesInformer.apiTypeClass());
- beanFactory.registerResolvableDependency(listerType.resolve(), lister);
+ RootBeanDefinition listerBean = new RootBeanDefinition();
+ listerBean.setTargetType(listerType);
+ listerBean.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
+ listerBean.setAutowireCandidate(true);
+ String listerBeanName = listerType.toString();
+ this.beanDefinitionRegistry.registerBeanDefinition(listerBeanName, listerBean);
+ beanFactory.registerSingleton(listerBeanName, lister);
}
}
@@ -137,4 +158,10 @@ public Watchable watch(CallGeneratorParams params) throws ApiException {
public int getOrder() {
return 0;
}
+
+ @Override
+ public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
+ throws BeansException {
+ this.beanDefinitionRegistry = registry;
+ }
}