From 11b140495aa9b6ec1801e6b129617a04918a2d05 Mon Sep 17 00:00:00 2001 From: 1nval1d Date: Sun, 9 Feb 2025 10:34:16 +0100 Subject: [PATCH 1/3] feature and refactor: add lombok, introduce package structure and add interface to configure dependent resources (#1) * refactor: add lombok * feature: add interface to configure dependent resources --- .../starter/test/EnableMockOperatorTests.java | 4 +- starter/pom.xml | 6 + .../springboot/starter/CRDApplier.java | 95 ---------- .../DependentResourceConfigurator.java | 8 + .../starter/KubernetesClientProperties.java | 77 -------- .../starter/OperatorAutoConfiguration.java | 166 +++++++----------- .../OperatorConfigurationProperties.java | 152 ---------------- .../starter/OperatorHealthIndicator.java | 3 +- .../springboot/starter/OperatorStarter.java | 37 ++-- .../starter/ReconcilerProperties.java | 79 --------- .../starter/ReconcilerRegistrationUtil.java | 69 ++++++++ .../springboot/starter/RetryProperties.java | 46 ----- .../springboot/starter/crd/CRDApplier.java | 11 ++ .../starter/crd/CRDTransformer.java | 16 ++ .../starter/crd/DefaultCRDApplier.java | 84 +++++++++ .../starter/properties/CrdProperties.java | 20 +++ .../KubernetesClientProperties.java | 42 +++++ .../OperatorConfigurationProperties.java | 32 ++++ .../properties/ReconcilerProperties.java | 21 +++ .../starter/properties/RetryProperties.java | 17 ++ .../AutoConfigurationIntegrationTest.java | 13 +- .../starter/AutoConfigurationTest.java | 18 +- .../springboot/starter/CRDApplierTest.java | 14 +- .../starter/CrdApplierConfigurationTest.java | 8 +- .../starter/OperatorStarterTest.java | 8 +- .../ConfigMapDRConfigurator.java | 21 +++ .../DependentResourceConfigMap.java | 23 +++ ...ntResourceConfiguratorIntegrationTest.java | 55 ++++++ .../DependentResourceReconciler.java | 17 ++ .../META-INF/fabric8/deeper/test-crd-v1.yml | 6 +- .../META-INF/fabric8/test-crd-v2.yml | 2 +- 31 files changed, 564 insertions(+), 606 deletions(-) delete mode 100644 starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/CRDApplier.java create mode 100644 starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/DependentResourceConfigurator.java delete mode 100644 starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/KubernetesClientProperties.java delete mode 100644 starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/OperatorConfigurationProperties.java delete mode 100644 starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/ReconcilerProperties.java create mode 100644 starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/ReconcilerRegistrationUtil.java delete mode 100644 starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/RetryProperties.java create mode 100644 starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/crd/CRDApplier.java create mode 100644 starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/crd/CRDTransformer.java create mode 100644 starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/crd/DefaultCRDApplier.java create mode 100644 starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/CrdProperties.java create mode 100644 starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/KubernetesClientProperties.java create mode 100644 starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/OperatorConfigurationProperties.java create mode 100644 starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/ReconcilerProperties.java create mode 100644 starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/RetryProperties.java create mode 100644 starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/dependentresourceconfigurator/ConfigMapDRConfigurator.java create mode 100644 starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/dependentresourceconfigurator/DependentResourceConfigMap.java create mode 100644 starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/dependentresourceconfigurator/DependentResourceConfiguratorIntegrationTest.java create mode 100644 starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/dependentresourceconfigurator/DependentResourceReconciler.java diff --git a/starter-test/src/test/java/io/javaoperatorsdk/operator/springboot/starter/test/EnableMockOperatorTests.java b/starter-test/src/test/java/io/javaoperatorsdk/operator/springboot/starter/test/EnableMockOperatorTests.java index 464944a..7b4edf6 100644 --- a/starter-test/src/test/java/io/javaoperatorsdk/operator/springboot/starter/test/EnableMockOperatorTests.java +++ b/starter-test/src/test/java/io/javaoperatorsdk/operator/springboot/starter/test/EnableMockOperatorTests.java @@ -22,8 +22,8 @@ import io.javaoperatorsdk.operator.api.config.ConfigurationService; import io.javaoperatorsdk.operator.api.config.ConfigurationServiceOverrider; import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; -import io.javaoperatorsdk.operator.springboot.starter.OperatorConfigurationProperties; -import io.javaoperatorsdk.operator.springboot.starter.ReconcilerProperties; +import io.javaoperatorsdk.operator.springboot.starter.properties.OperatorConfigurationProperties; +import io.javaoperatorsdk.operator.springboot.starter.properties.ReconcilerProperties; import io.javaoperatorsdk.operator.springboot.starter.sample.CustomService; import io.javaoperatorsdk.operator.springboot.starter.sample.CustomServiceReconciler; import io.javaoperatorsdk.operator.springboot.starter.sample.ServiceSpec; diff --git a/starter/pom.xml b/starter/pom.xml index 6df69dc..8ced795 100644 --- a/starter/pom.xml +++ b/starter/pom.xml @@ -38,6 +38,12 @@ + + org.projectlombok + lombok + 1.18.36 + provided + org.springframework.boot spring-boot-autoconfigure-processor diff --git a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/CRDApplier.java b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/CRDApplier.java deleted file mode 100644 index 5b7364e..0000000 --- a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/CRDApplier.java +++ /dev/null @@ -1,95 +0,0 @@ -package io.javaoperatorsdk.operator.springboot.starter; - -import java.io.IOException; -import java.nio.file.Paths; -import java.util.List; -import java.util.function.UnaryOperator; - -import org.slf4j.Logger; -import org.springframework.core.io.Resource; -import org.springframework.core.io.support.PathMatchingResourcePatternResolver; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.fabric8.kubernetes.api.model.ObjectMeta; -import io.fabric8.kubernetes.client.KubernetesClient; - -import static java.util.Arrays.stream; -import static java.util.stream.Collectors.joining; -import static org.slf4j.LoggerFactory.getLogger; - -@FunctionalInterface -public interface CRDApplier { - - CRDApplier NOOP = () -> getLogger(CRDApplier.class).debug("Not searching for CRDs to apply"); - - void apply(); - - interface CRDTransformer extends UnaryOperator { - default CRDTransformer thenTransform(CRDApplier.CRDTransformer after) { - return t -> after.apply(apply(t)); - } - - static CRDTransformer reduce(List transformers) { - return transformers.stream().reduce(t -> t, CRDTransformer::thenTransform); - } - } - - class DefaultCRDApplier implements CRDApplier { - - private static final Logger log = getLogger(DefaultCRDApplier.class); - - private static final int CRD_READY_WAIT = 2000; - - private final CRDTransformer crdTransformer; - private final KubernetesClient kubernetesClient; - private final String crdSuffix; - private final String crdPath; - - public DefaultCRDApplier(KubernetesClient kubernetesClient, List transformers, - String crdPath, String crdSuffix) { - this.crdTransformer = CRDTransformer.reduce(transformers); - this.kubernetesClient = kubernetesClient; - this.crdSuffix = crdSuffix; - this.crdPath = crdPath; - } - - @Override - public void apply() { - log.debug("Uploading CRDs with suffix {} under {}", crdSuffix, crdPath); - stream(findResources()).forEach(this::applyCrd); - } - - private Resource[] findResources() { - final var resourceResolver = new PathMatchingResourcePatternResolver(); - final var resourceLocationPattern = Paths.get(crdPath, '*' + crdSuffix).toString(); - try { - return resourceResolver.getResources(resourceLocationPattern); - } catch (IOException e) { - throw new RuntimeException( - "could not find CRD resources from the location pattern: " + resourceLocationPattern); - } - } - - private void applyCrd(Resource crdResource) { - try (var is = crdResource.getInputStream()) { - var crds = kubernetesClient.load(is).items().stream().map(crdTransformer).toList(); - kubernetesClient.resourceList(crds).createOrReplace(); - - Thread.sleep(CRD_READY_WAIT); // readiness is not applicable for CRD, just wait a little - - logUploaded(crds); - } catch (InterruptedException | IOException ex) { - throw new RuntimeException(ex); - } - } - - private void logUploaded(List crds) { - var crdNames = crds.stream() - .map(HasMetadata::getMetadata) - .map(ObjectMeta::getName) - .collect(joining(", ")); - log.info("Uploaded CRDs: {}", crdNames); - } - - } -} diff --git a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/DependentResourceConfigurator.java b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/DependentResourceConfigurator.java new file mode 100644 index 0000000..80cadce --- /dev/null +++ b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/DependentResourceConfigurator.java @@ -0,0 +1,8 @@ +package io.javaoperatorsdk.operator.springboot.starter; + +@FunctionalInterface +public interface DependentResourceConfigurator { + + String getName(); + +} diff --git a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/KubernetesClientProperties.java b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/KubernetesClientProperties.java deleted file mode 100644 index 57dcf60..0000000 --- a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/KubernetesClientProperties.java +++ /dev/null @@ -1,77 +0,0 @@ -package io.javaoperatorsdk.operator.springboot.starter; - -import java.util.Optional; - -public class KubernetesClientProperties { - - private boolean openshift = false; - private String context; - private String username; - private String password; - private String oauthToken; - private String masterUrl; - private boolean trustSelfSignedCertificates = false; - - public boolean isOpenshift() { - return openshift; - } - - public KubernetesClientProperties setOpenshift(boolean openshift) { - this.openshift = openshift; - return this; - } - - public Optional getContext() { - return Optional.ofNullable(context); - } - - public void setContext(String context) { - this.context = context; - } - - public Optional getUsername() { - return Optional.ofNullable(username); - } - - public KubernetesClientProperties setUsername(String username) { - this.username = username; - return this; - } - - public Optional getPassword() { - return Optional.ofNullable(password); - } - - public KubernetesClientProperties setPassword(String password) { - this.password = password; - return this; - } - - public Optional getOauthToken() { - return Optional.ofNullable(oauthToken); - } - - public KubernetesClientProperties setOauthToken(String oauthToken) { - this.oauthToken = oauthToken; - return this; - } - - public Optional getMasterUrl() { - return Optional.ofNullable(masterUrl); - } - - public KubernetesClientProperties setMasterUrl(String masterUrl) { - this.masterUrl = masterUrl; - return this; - } - - public boolean isTrustSelfSignedCertificates() { - return trustSelfSignedCertificates; - } - - public KubernetesClientProperties setTrustSelfSignedCertificates( - boolean trustSelfSignedCertificates) { - this.trustSelfSignedCertificates = trustSelfSignedCertificates; - return this; - } -} diff --git a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/OperatorAutoConfiguration.java b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/OperatorAutoConfiguration.java index 7c17585..7555bf1 100644 --- a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/OperatorAutoConfiguration.java +++ b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/OperatorAutoConfiguration.java @@ -1,18 +1,15 @@ package io.javaoperatorsdk.operator.springboot.starter; import java.util.List; -import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.function.BiConsumer; import java.util.function.Consumer; +import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -28,14 +25,12 @@ import io.javaoperatorsdk.operator.ReconcilerUtils; import io.javaoperatorsdk.operator.api.config.Cloner; import io.javaoperatorsdk.operator.api.config.ConfigurationServiceOverrider; -import io.javaoperatorsdk.operator.api.config.ControllerConfigurationOverrider; import io.javaoperatorsdk.operator.api.config.DefaultResourceClassResolver; import io.javaoperatorsdk.operator.api.config.ResourceClassResolver; import io.javaoperatorsdk.operator.api.monitoring.Metrics; import io.javaoperatorsdk.operator.api.reconciler.Reconciler; -import io.javaoperatorsdk.operator.processing.retry.GenericRetry; -import io.javaoperatorsdk.operator.springboot.starter.CRDApplier.CRDTransformer; -import io.javaoperatorsdk.operator.springboot.starter.CRDApplier.DefaultCRDApplier; +import io.javaoperatorsdk.operator.springboot.starter.crd.CRDApplier; +import io.javaoperatorsdk.operator.springboot.starter.properties.OperatorConfigurationProperties; @Configuration @EnableConfigurationProperties(OperatorConfigurationProperties.class) @@ -44,25 +39,23 @@ public class OperatorAutoConfiguration { private final static Logger log = LoggerFactory.getLogger(OperatorAutoConfiguration.class); @Autowired - private OperatorConfigurationProperties configuration; + private OperatorConfigurationProperties configurationProperties; @Bean @ConditionalOnMissingBean - public KubernetesClient kubernetesClient(Optional httpClientFactory, - Config config) { - return configuration.getClient().isOpenshift() - ? httpClientFactory - .map(it -> new KubernetesClientBuilder().withHttpClientFactory(it).withConfig(config) - .build().adapt(OpenShiftClient.class)) - // new DefaultOpenShiftClient(it.createHttpClient(config), - // new OpenShiftConfig(config))) - .orElseGet(() -> new KubernetesClientBuilder().withConfig(config) - .build().adapt(OpenShiftClient.class)) - : httpClientFactory - .map(it -> new KubernetesClientBuilder().withHttpClientFactory(it).withConfig(config) - .build()) - .orElseGet(() -> new KubernetesClientBuilder().withConfig(config) - .build()); + public KubernetesClient kubernetesClient( + @Autowired(required = false) HttpClient.Factory httpClientFactory, Config config) { + KubernetesClientBuilder clientBuilder = new KubernetesClientBuilder().withConfig(config); + + if (httpClientFactory != null) { + clientBuilder = clientBuilder.withHttpClientFactory(httpClientFactory); + } + KubernetesClient client = clientBuilder.build(); + + if (configurationProperties.getClient().isOpenshift()) { + return client.adapt(OpenShiftClient.class); + } + return client; } @Bean @@ -71,107 +64,78 @@ public ResourceClassResolver resourceClassResolver() { return new DefaultResourceClassResolver(); } - @Bean - @ConditionalOnProperty(value = "javaoperatorsdk.crd.apply-on-startup", havingValue = "true") - public CRDApplier crdApplier(KubernetesClient client, List transformers) { - var crd = configuration.getCrd(); - return new DefaultCRDApplier(client, transformers, crd.getPath(), crd.getSuffix()); - } - @Bean @ConditionalOnMissingBean(CRDApplier.class) public CRDApplier disabledCrdApplier() { + if (log.isDebugEnabled()) { + log.debug("no CRDApplier loaded, using NOOP"); + } return CRDApplier.NOOP; } - @Bean - public OperatorStarter operatorStarter(Operator operator, CRDApplier applier) { - return new OperatorStarter(operator, applier); - } - @Bean(destroyMethod = "stop") @ConditionalOnMissingBean(Operator.class) - public Operator operator( - BiConsumer> reconcilerRegisterer, - @Qualifier("compositeConfigurationServiceOverrider") Consumer compositeConfigurationServiceOverrider, - KubernetesClient kubernetesClient, - List> reconcilers) { - - var operator = new Operator(compositeConfigurationServiceOverrider); - reconcilers.forEach(reconciler -> reconcilerRegisterer.accept(operator, reconciler)); - - return operator; - } - - @Bean - public BiConsumer> reconcilerRegisterer() { - return (operator, reconciler) -> { - var name = ReconcilerUtils.getNameFor(reconciler); - var props = configuration.getReconcilers().get(name); - - operator.register(reconciler, overrider -> overrideFromProps(overrider, props)); - }; - } - - @Bean - public Consumer compositeConfigurationServiceOverrider( - List> configServiceOverriders) { - return configServiceOverriders.stream() + public Operator operator(List> configServiceOverriders, + List> reconcilers, + List dependentResourceConfigurators) { + var chainedOverriders = configServiceOverriders.stream() .reduce(Consumer::andThen) .orElseThrow( () -> new IllegalStateException("Default Config Service Overrider Not Created")); + + var dependentResourceConfiguratorsMap = dependentResourceConfigurators == null ? null + : dependentResourceConfigurators.stream() + .collect(Collectors.groupingBy(DependentResourceConfigurator::getName)); + + var operator = new Operator(chainedOverriders); + reconcilers.forEach(reconciler -> { + var name = ReconcilerUtils.getNameFor(reconciler); + var props = configurationProperties.getReconcilers().get(name); + operator.register(reconciler, o -> { + ReconcilerRegistrationUtil.overrideFromProps(o, props); + if (dependentResourceConfiguratorsMap != null) { + var drsForReconciler = ReconcilerRegistrationUtil.filterConfigurators(reconciler, + dependentResourceConfiguratorsMap); + drsForReconciler.forEach(dr -> o.replacingNamedDependentResourceConfig(dr.getName(), dr)); + } + }); + }); + return operator; } @Bean @Order(0) public Consumer defaultConfigServiceOverrider( - @Autowired(required = false) Cloner cloner, - Metrics metrics, KubernetesClient kubernetesClient) { + @Autowired(required = false) Cloner cloner, Metrics metrics, + KubernetesClient kubernetesClient) { return overrider -> { - doIfPresent(cloner, overrider::withResourceCloner); - doIfPresent(configuration.getStopOnInformerErrorDuringStartup(), + ReconcilerRegistrationUtil.doIfPresent(cloner, overrider::withResourceCloner); + ReconcilerRegistrationUtil.doIfPresent( + configurationProperties.getStopOnInformerErrorDuringStartup(), overrider::withStopOnInformerErrorDuringStartup); - doIfPresent(configuration.getConcurrentWorkflowExecutorThreads(), + ReconcilerRegistrationUtil.doIfPresent( + configurationProperties.getConcurrentWorkflowExecutorThreads(), overrider::withConcurrentWorkflowExecutorThreads); - doIfPresent(configuration.getCloseClientOnStop(), overrider::withCloseClientOnStop); - doIfPresent(configuration.getCacheSyncTimeout(), overrider::withCacheSyncTimeout); + ReconcilerRegistrationUtil.doIfPresent(configurationProperties.getCloseClientOnStop(), + overrider::withCloseClientOnStop); + ReconcilerRegistrationUtil.doIfPresent(configurationProperties.getCacheSyncTimeout(), + overrider::withCacheSyncTimeout); + overrider - .withConcurrentReconciliationThreads(configuration.getConcurrentReconciliationThreads()) + .withConcurrentReconciliationThreads( + configurationProperties.getConcurrentReconciliationThreads()) .withMetrics(metrics) - .checkingCRDAndValidateLocalModel(configuration.getCheckCrdAndValidateLocalModel()) + .checkingCRDAndValidateLocalModel( + configurationProperties.isCheckCrdAndValidateLocalModel()) .withKubernetesClient(kubernetesClient); }; } - private void overrideFromProps(ControllerConfigurationOverrider overrider, - ReconcilerProperties props) { - if (props != null) { - doIfPresent(props.getFinalizerName(), overrider::withFinalizer); - doIfPresent(props.getName(), overrider::withName); - doIfPresent(props.getNamespaces(), overrider::settingNamespaces); - doIfPresent(props.getRetry(), r -> { - var retry = new GenericRetry(); - doIfPresent(r.getInitialInterval(), retry::setInitialInterval); - doIfPresent(r.getMaxAttempts(), retry::setMaxAttempts); - doIfPresent(r.getMaxInterval(), retry::setMaxInterval); - doIfPresent(r.getIntervalMultiplier(), retry::setIntervalMultiplier); - overrider.withRetry(retry); - }); - doIfPresent(props.isGenerationAware(), overrider::withGenerationAware); - doIfPresent(props.isClusterScoped(), clusterScoped -> { - if (clusterScoped) { - overrider.watchingAllNamespaces(); - } - }); - doIfPresent(props.getLabelSelector(), overrider::withLabelSelector); - doIfPresent(props.getReconciliationMaxInterval(), overrider::withReconciliationMaxInterval); - } - } - @Bean @ConditionalOnMissingBean(name = "reconciliationExecutorService") public ExecutorService reconciliationExecutorService() { - return Executors.newFixedThreadPool(configuration.getConcurrentReconciliationThreads()); + return Executors + .newFixedThreadPool(configurationProperties.getConcurrentReconciliationThreads()); } @Bean @@ -183,7 +147,7 @@ public Metrics metrics() { @Bean public Config getClientConfiguration( @Autowired(required = false) KubernetesConfigCustomizer configCustomizer) { - return configuration.getClient().getContext() + return configurationProperties.getClient().getContext() .map(Config::autoConfigure) .map(it -> { if (configCustomizer != null) { @@ -195,7 +159,7 @@ public Config getClientConfiguration( } }) .orElseGet(() -> { - final var clientCfg = configuration.getClient(); + final var clientCfg = configurationProperties.getClient(); ConfigBuilder config = new ConfigBuilder(); config.withTrustCerts(clientCfg.isTrustSelfSignedCertificates()); clientCfg.getMasterUrl().ifPresent(config::withMasterUrl); @@ -210,8 +174,4 @@ public Config getClientConfiguration( }); } - private void doIfPresent(T prop, Consumer action) { - Optional.ofNullable(prop).ifPresent(action); - } - } diff --git a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/OperatorConfigurationProperties.java b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/OperatorConfigurationProperties.java deleted file mode 100644 index 18b4c3c..0000000 --- a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/OperatorConfigurationProperties.java +++ /dev/null @@ -1,152 +0,0 @@ -package io.javaoperatorsdk.operator.springboot.starter; - -import java.time.Duration; -import java.util.Collections; -import java.util.Map; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -import io.javaoperatorsdk.operator.api.config.ConfigurationService; - -@ConfigurationProperties(prefix = "javaoperatorsdk") -public class OperatorConfigurationProperties { - - private CrdProperties crd = new CrdProperties(); - private KubernetesClientProperties client = new KubernetesClientProperties(); - private Map reconcilers = Collections.emptyMap(); - private boolean checkCrdAndValidateLocalModel = true; - private int concurrentReconciliationThreads = - ConfigurationService.DEFAULT_RECONCILIATION_THREADS_NUMBER; - private Integer minConcurrentReconciliationThreads; - private Integer concurrentWorkflowExecutorThreads; - private Integer minConcurrentWorkflowExecutorThreads; - private Boolean closeClientOnStop; - private Boolean stopOnInformerErrorDuringStartup; - private Duration cacheSyncTimeout; - - public KubernetesClientProperties getClient() { - return client; - } - - public void setClient(KubernetesClientProperties client) { - this.client = client; - } - - public Map getReconcilers() { - return reconcilers; - } - - public void setReconcilers(Map reconcilers) { - this.reconcilers = reconcilers; - } - - public boolean getCheckCrdAndValidateLocalModel() { - return checkCrdAndValidateLocalModel; - } - - public void setCheckCrdAndValidateLocalModel(boolean checkCrdAndValidateLocalModel) { - this.checkCrdAndValidateLocalModel = checkCrdAndValidateLocalModel; - } - - public int getConcurrentReconciliationThreads() { - return concurrentReconciliationThreads; - } - - public void setConcurrentReconciliationThreads(int concurrentReconciliationThreads) { - this.concurrentReconciliationThreads = concurrentReconciliationThreads; - } - - public Integer getMinConcurrentReconciliationThreads() { - return minConcurrentReconciliationThreads; - } - - public void setMinConcurrentReconciliationThreads(Integer minConcurrentReconciliationThreads) { - this.minConcurrentReconciliationThreads = minConcurrentReconciliationThreads; - } - - public Integer getConcurrentWorkflowExecutorThreads() { - return concurrentWorkflowExecutorThreads; - } - - public void setConcurrentWorkflowExecutorThreads(Integer concurrentWorkflowExecutorThreads) { - this.concurrentWorkflowExecutorThreads = concurrentWorkflowExecutorThreads; - } - - public Integer getMinConcurrentWorkflowExecutorThreads() { - return minConcurrentWorkflowExecutorThreads; - } - - public void setMinConcurrentWorkflowExecutorThreads( - Integer minConcurrentWorkflowExecutorThreads) { - this.minConcurrentWorkflowExecutorThreads = minConcurrentWorkflowExecutorThreads; - } - - public Boolean getCloseClientOnStop() { - return closeClientOnStop; - } - - public void setCloseClientOnStop(Boolean closeClientOnStop) { - this.closeClientOnStop = closeClientOnStop; - } - - public Boolean getStopOnInformerErrorDuringStartup() { - return stopOnInformerErrorDuringStartup; - } - - public void setStopOnInformerErrorDuringStartup(Boolean stopOnInformerErrorDuringStartup) { - this.stopOnInformerErrorDuringStartup = stopOnInformerErrorDuringStartup; - } - - public Duration getCacheSyncTimeout() { - return cacheSyncTimeout; - } - - public void setCacheSyncTimeout(Duration cacheSyncTimeout) { - this.cacheSyncTimeout = cacheSyncTimeout; - } - - public CrdProperties getCrd() { - return crd; - } - - public void setCrd(CrdProperties crd) { - this.crd = crd; - } - - public static class CrdProperties { - - private boolean applyOnStartup; - /** - * path to the resource folder where CRDs are located - */ - private String path = "/META-INF/fabric8/"; - /** - * file suffix to filter out CRDs - */ - private String suffix = "-v1.yml"; - - public boolean isApplyOnStartup() { - return applyOnStartup; - } - - public void setApplyOnStartup(boolean applyOnStartup) { - this.applyOnStartup = applyOnStartup; - } - - public String getPath() { - return path; - } - - public void setPath(String path) { - this.path = path; - } - - public String getSuffix() { - return suffix; - } - - public void setSuffix(String suffix) { - this.suffix = suffix; - } - } -} diff --git a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/OperatorHealthIndicator.java b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/OperatorHealthIndicator.java index 8f28782..698e999 100644 --- a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/OperatorHealthIndicator.java +++ b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/OperatorHealthIndicator.java @@ -12,9 +12,8 @@ @Component public class OperatorHealthIndicator extends AbstractHealthIndicator { - private final Operator operator; - private final static Logger log = LoggerFactory.getLogger(OperatorHealthIndicator.class); + private final Operator operator; public OperatorHealthIndicator(final Operator operator) { super("OperatorSDK health check failed"); diff --git a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/OperatorStarter.java b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/OperatorStarter.java index e75235b..5a572af 100644 --- a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/OperatorStarter.java +++ b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/OperatorStarter.java @@ -5,35 +5,38 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; import io.javaoperatorsdk.operator.Operator; +import io.javaoperatorsdk.operator.springboot.starter.crd.CRDApplier; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; + +@Component +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class OperatorStarter { private static final Logger log = LoggerFactory.getLogger(OperatorStarter.class); - private final Operator operator; - private final CRDApplier crdApplier; - - public OperatorStarter(Operator operator, CRDApplier crdApplier) { - this.operator = operator; - this.crdApplier = crdApplier; - } + Operator operator; + CRDApplier crdApplier; @EventListener public void start(ApplicationReadyEvent event) { - if (!operator.getRegisteredControllers().isEmpty()) { - try { - crdApplier.apply(); - operator.start(); - } catch (Exception ex) { - log.error("Could not start operator", ex); - SpringApplication.exit(event.getApplicationContext(), () -> 1); - } - } else { + if (operator.getRegisteredControllers().isEmpty()) { log.warn( "No Reconcilers found in the application context: Not starting the Operator, not looking for CRDs"); + return; + } + try { + crdApplier.apply(); + operator.start(); + } catch (Exception ex) { + log.error("Could not start operator", ex); + SpringApplication.exit(event.getApplicationContext(), () -> 1); } } - } diff --git a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/ReconcilerProperties.java b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/ReconcilerProperties.java deleted file mode 100644 index 8987f8d..0000000 --- a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/ReconcilerProperties.java +++ /dev/null @@ -1,79 +0,0 @@ -package io.javaoperatorsdk.operator.springboot.starter; - -import java.time.Duration; -import java.util.Set; - -public class ReconcilerProperties { - private String name; - private String finalizerName; - private Boolean generationAware; - private Boolean clusterScoped; - private Set namespaces; - private RetryProperties retry; - private String labelSelector; - private Duration reconciliationMaxInterval; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getFinalizerName() { - return finalizerName; - } - - public void setFinalizerName(String finalizerName) { - this.finalizerName = finalizerName; - } - - public Boolean isGenerationAware() { - return generationAware; - } - - public void setGenerationAware(Boolean generationAware) { - this.generationAware = generationAware; - } - - public Boolean isClusterScoped() { - return clusterScoped; - } - - public void setClusterScoped(Boolean clusterScoped) { - this.clusterScoped = clusterScoped; - } - - public Set getNamespaces() { - return namespaces; - } - - public void setNamespaces(Set namespaces) { - this.namespaces = namespaces; - } - - public RetryProperties getRetry() { - return retry; - } - - public void setRetry(RetryProperties retry) { - this.retry = retry; - } - - public String getLabelSelector() { - return labelSelector; - } - - public void setLabelSelector(String labelSelector) { - this.labelSelector = labelSelector; - } - - public Duration getReconciliationMaxInterval() { - return reconciliationMaxInterval; - } - - public void setReconciliationMaxInterval(Duration reconciliationMaxInterval) { - this.reconciliationMaxInterval = reconciliationMaxInterval; - } -} diff --git a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/ReconcilerRegistrationUtil.java b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/ReconcilerRegistrationUtil.java new file mode 100644 index 0000000..d61a32d --- /dev/null +++ b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/ReconcilerRegistrationUtil.java @@ -0,0 +1,69 @@ +package io.javaoperatorsdk.operator.springboot.starter; + +import java.util.*; +import java.util.function.Consumer; + +import io.javaoperatorsdk.operator.api.config.ControllerConfigurationOverrider; +import io.javaoperatorsdk.operator.api.reconciler.Reconciler; +import io.javaoperatorsdk.operator.api.reconciler.Workflow; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; +import io.javaoperatorsdk.operator.processing.retry.GenericRetry; +import io.javaoperatorsdk.operator.springboot.starter.properties.ReconcilerProperties; + +import lombok.experimental.UtilityClass; + +@UtilityClass +public class ReconcilerRegistrationUtil { + + public List filterConfigurators(Reconciler reconciler, + Map> configuratorsMap) { + var workflow = reconciler.getClass().getAnnotation(Workflow.class); + if (workflow == null) + return Collections.emptyList(); + var dependents = workflow.dependents(); + ArrayList relevant = new ArrayList<>(); + for (Dependent dependent : dependents) { + var name = dependent.name(); + var configurators = configuratorsMap.get(name); + if (configurators == null || configurators.isEmpty()) { + continue; + } + if (configurators.size() > 1) { + throw new IllegalStateException("more than one config for Dependent Resource " + name + + " - " + configurators.stream().map(o -> o.getClass().getName()).toList()); + } + relevant.add(configurators.get(0)); + } + return relevant; + } + + + public void overrideFromProps(ControllerConfigurationOverrider overrider, + ReconcilerProperties props) { + if (props != null) { + doIfPresent(props.getFinalizerName(), overrider::withFinalizer); + doIfPresent(props.getName(), overrider::withName); + doIfPresent(props.getNamespaces(), overrider::settingNamespaces); + doIfPresent(props.getRetry(), r -> { + var retry = new GenericRetry(); + doIfPresent(r.getInitialInterval(), retry::setInitialInterval); + doIfPresent(r.getMaxAttempts(), retry::setMaxAttempts); + doIfPresent(r.getMaxInterval(), retry::setMaxInterval); + doIfPresent(r.getIntervalMultiplier(), retry::setIntervalMultiplier); + overrider.withRetry(retry); + }); + doIfPresent(props.getGenerationAware(), overrider::withGenerationAware); + doIfPresent(props.getClusterScoped(), clusterScoped -> { + if (clusterScoped) { + overrider.watchingAllNamespaces(); + } + }); + doIfPresent(props.getLabelSelector(), overrider::withLabelSelector); + doIfPresent(props.getReconciliationMaxInterval(), overrider::withReconciliationMaxInterval); + } + } + + public void doIfPresent(T prop, Consumer action) { + Optional.ofNullable(prop).ifPresent(action); + } +} diff --git a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/RetryProperties.java b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/RetryProperties.java deleted file mode 100644 index 8c75090..0000000 --- a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/RetryProperties.java +++ /dev/null @@ -1,46 +0,0 @@ -package io.javaoperatorsdk.operator.springboot.starter; - -public class RetryProperties { - - private Integer maxAttempts; - private Long initialInterval; - private Double intervalMultiplier; - private Long maxInterval; - - public Integer getMaxAttempts() { - return maxAttempts; - } - - public RetryProperties setMaxAttempts(Integer maxAttempts) { - this.maxAttempts = maxAttempts; - return this; - } - - public Long getInitialInterval() { - return initialInterval; - } - - public RetryProperties setInitialInterval(Long initialInterval) { - this.initialInterval = initialInterval; - return this; - } - - public Double getIntervalMultiplier() { - return intervalMultiplier; - } - - public RetryProperties setIntervalMultiplier(Double intervalMultiplier) { - this.intervalMultiplier = intervalMultiplier; - return this; - } - - public Long getMaxInterval() { - return maxInterval; - } - - public RetryProperties setMaxInterval(Long maxInterval) { - this.maxInterval = maxInterval; - return this; - } - -} diff --git a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/crd/CRDApplier.java b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/crd/CRDApplier.java new file mode 100644 index 0000000..6888498 --- /dev/null +++ b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/crd/CRDApplier.java @@ -0,0 +1,11 @@ +package io.javaoperatorsdk.operator.springboot.starter.crd; + +import static org.slf4j.LoggerFactory.getLogger; + +@FunctionalInterface +public interface CRDApplier { + + CRDApplier NOOP = () -> getLogger(CRDApplier.class).debug("Not searching for CRDs to apply"); + + void apply(); +} diff --git a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/crd/CRDTransformer.java b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/crd/CRDTransformer.java new file mode 100644 index 0000000..309347e --- /dev/null +++ b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/crd/CRDTransformer.java @@ -0,0 +1,16 @@ +package io.javaoperatorsdk.operator.springboot.starter.crd; + +import java.util.List; +import java.util.function.UnaryOperator; + +import io.fabric8.kubernetes.api.model.HasMetadata; + +public interface CRDTransformer extends UnaryOperator { + static CRDTransformer reduce(List transformers) { + return transformers.stream().reduce(t -> t, CRDTransformer::thenTransform); + } + + default CRDTransformer thenTransform(CRDTransformer after) { + return t -> after.apply(apply(t)); + } +} diff --git a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/crd/DefaultCRDApplier.java b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/crd/DefaultCRDApplier.java new file mode 100644 index 0000000..561ae59 --- /dev/null +++ b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/crd/DefaultCRDApplier.java @@ -0,0 +1,84 @@ +package io.javaoperatorsdk.operator.springboot.starter.crd; + +import java.io.IOException; +import java.nio.file.Paths; +import java.util.List; + +import org.slf4j.Logger; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.stereotype.Component; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.javaoperatorsdk.operator.springboot.starter.properties.OperatorConfigurationProperties; + +import lombok.AccessLevel; +import lombok.experimental.FieldDefaults; + +import static java.util.Arrays.stream; +import static java.util.stream.Collectors.joining; +import static org.slf4j.LoggerFactory.getLogger; + +@Component +@ConditionalOnProperty(value = "javaoperatorsdk.crd.apply-on-startup", havingValue = "true") +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class DefaultCRDApplier implements CRDApplier { + + private static final Logger log = getLogger(DefaultCRDApplier.class); + private static final int CRD_READY_WAIT = 2000; + + KubernetesClient kubernetesClient; + CRDTransformer crdTransformer; + String crdSuffix; + String crdPath; + + public DefaultCRDApplier(KubernetesClient kubernetesClient, + OperatorConfigurationProperties configurationProperties, List transformers) { + this.kubernetesClient = kubernetesClient; + this.crdSuffix = configurationProperties.getCrd().getSuffix(); + this.crdPath = configurationProperties.getCrd().getPath(); + this.crdTransformer = CRDTransformer.reduce(transformers); + } + + @Override + public void apply() { + log.debug("Uploading CRDs with suffix {} under {}", crdSuffix, crdPath); + stream(findResources()).forEach(this::applyCrd); + } + + private Resource[] findResources() { + final var resourceResolver = new PathMatchingResourcePatternResolver(); + final var resourceLocationPattern = Paths.get(crdPath, '*' + crdSuffix).toString(); + try { + return resourceResolver.getResources(resourceLocationPattern); + } catch (IOException e) { + throw new RuntimeException( + "could not find CRD resources from the location pattern: " + resourceLocationPattern); + } + } + + private void applyCrd(Resource crdResource) { + try (var is = crdResource.getInputStream()) { + var crds = kubernetesClient.load(is).items().stream().map(crdTransformer).toList(); + kubernetesClient.resourceList(crds).serverSideApply(); + + Thread.sleep(CRD_READY_WAIT); // readiness is not applicable for CRD, just wait a little + + logUploaded(crds); + } catch (InterruptedException | IOException ex) { + throw new RuntimeException(ex); + } + } + + private void logUploaded(List crds) { + var crdNames = crds.stream() + .map(HasMetadata::getMetadata) + .map(ObjectMeta::getName) + .collect(joining(", ")); + log.info("Uploaded CRDs: {}", crdNames); + } + +} diff --git a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/CrdProperties.java b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/CrdProperties.java new file mode 100644 index 0000000..68a6129 --- /dev/null +++ b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/CrdProperties.java @@ -0,0 +1,20 @@ +package io.javaoperatorsdk.operator.springboot.starter.properties; + +import lombok.AccessLevel; +import lombok.Data; +import lombok.experimental.FieldDefaults; + +@Data +@FieldDefaults(level = AccessLevel.PRIVATE) +public class CrdProperties { + + boolean applyOnStartup; + /** + * path to the resource folder where CRDs are located + */ + String path = "/META-INF/fabric8/"; + /** + * file suffix to filter out CRDs + */ + String suffix = "-v1.yml"; +} diff --git a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/KubernetesClientProperties.java b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/KubernetesClientProperties.java new file mode 100644 index 0000000..ed24e8d --- /dev/null +++ b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/KubernetesClientProperties.java @@ -0,0 +1,42 @@ +package io.javaoperatorsdk.operator.springboot.starter.properties; + +import java.util.Optional; + +import lombok.AccessLevel; +import lombok.Data; +import lombok.experimental.Accessors; +import lombok.experimental.FieldDefaults; + +@Data +@Accessors(chain = true) +@FieldDefaults(level = AccessLevel.PRIVATE) +public class KubernetesClientProperties { + + boolean openshift = false; + String context; + String username; + String password; + String oauthToken; + String masterUrl; + boolean trustSelfSignedCertificates = false; + + public Optional getContext() { + return Optional.ofNullable(context); + } + + public Optional getUsername() { + return Optional.ofNullable(username); + } + + public Optional getPassword() { + return Optional.ofNullable(password); + } + + public Optional getOauthToken() { + return Optional.ofNullable(oauthToken); + } + + public Optional getMasterUrl() { + return Optional.ofNullable(masterUrl); + } +} diff --git a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/OperatorConfigurationProperties.java b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/OperatorConfigurationProperties.java new file mode 100644 index 0000000..f72049f --- /dev/null +++ b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/OperatorConfigurationProperties.java @@ -0,0 +1,32 @@ +package io.javaoperatorsdk.operator.springboot.starter.properties; + +import java.time.Duration; +import java.util.Collections; +import java.util.Map; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +import io.javaoperatorsdk.operator.api.config.ConfigurationService; + +import lombok.AccessLevel; +import lombok.Data; +import lombok.experimental.FieldDefaults; + +@Data +@FieldDefaults(level = AccessLevel.PRIVATE) +@ConfigurationProperties(prefix = "javaoperatorsdk") +public class OperatorConfigurationProperties { + + CrdProperties crd = new CrdProperties(); + KubernetesClientProperties client = new KubernetesClientProperties(); + + Map reconcilers = Collections.emptyMap(); + boolean checkCrdAndValidateLocalModel = true; + int concurrentReconciliationThreads = ConfigurationService.DEFAULT_RECONCILIATION_THREADS_NUMBER; + Integer minConcurrentReconciliationThreads; + Integer concurrentWorkflowExecutorThreads; + Integer minConcurrentWorkflowExecutorThreads; + Boolean closeClientOnStop; + Boolean stopOnInformerErrorDuringStartup; + Duration cacheSyncTimeout; +} diff --git a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/ReconcilerProperties.java b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/ReconcilerProperties.java new file mode 100644 index 0000000..4b90c01 --- /dev/null +++ b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/ReconcilerProperties.java @@ -0,0 +1,21 @@ +package io.javaoperatorsdk.operator.springboot.starter.properties; + +import java.time.Duration; +import java.util.Set; + +import lombok.AccessLevel; +import lombok.Data; +import lombok.experimental.FieldDefaults; + +@Data +@FieldDefaults(level = AccessLevel.PRIVATE) +public class ReconcilerProperties { + String name; + String finalizerName; + Boolean generationAware; + Boolean clusterScoped; + Set namespaces; + RetryProperties retry; + String labelSelector; + Duration reconciliationMaxInterval; +} diff --git a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/RetryProperties.java b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/RetryProperties.java new file mode 100644 index 0000000..e05d749 --- /dev/null +++ b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/RetryProperties.java @@ -0,0 +1,17 @@ +package io.javaoperatorsdk.operator.springboot.starter.properties; + +import lombok.AccessLevel; +import lombok.Data; +import lombok.experimental.Accessors; +import lombok.experimental.FieldDefaults; + +@Data +@Accessors(chain = true) +@FieldDefaults(level = AccessLevel.PRIVATE) +public class RetryProperties { + + Integer maxAttempts; + Long initialInterval; + Double intervalMultiplier; + Long maxInterval; +} diff --git a/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/AutoConfigurationIntegrationTest.java b/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/AutoConfigurationIntegrationTest.java index ca6fba2..9a564df 100644 --- a/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/AutoConfigurationIntegrationTest.java +++ b/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/AutoConfigurationIntegrationTest.java @@ -10,7 +10,7 @@ import io.fabric8.kubernetes.api.model.ListOptionsBuilder; import io.fabric8.kubernetes.client.KubernetesClient; import io.javaoperatorsdk.jenvtest.junit.EnableKubeAPIServer; -import io.javaoperatorsdk.operator.springboot.starter.CRDApplier.CRDTransformer; +import io.javaoperatorsdk.operator.springboot.starter.crd.CRDTransformer; import static org.assertj.core.api.Assertions.assertThat; @@ -42,22 +42,21 @@ void crdsUploadedAndTransformersApplied() { @TestConfiguration static class TestConfig { + private static HasMetadata addLabel(HasMetadata crd, String k, String v) { + crd.getMetadata().getLabels().put(k, v); + return crd; + } + @Bean public CRDTransformer transformerOne() { return crd -> addLabel(crd, "Glory", "Glory"); } - @Bean public CRDTransformer transformerTwo() { return crd -> addLabel(crd, "Man", "United"); } - private static HasMetadata addLabel(HasMetadata crd, String k, String v) { - crd.getMetadata().getLabels().put(k, v); - return crd; - } - } } diff --git a/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/AutoConfigurationTest.java b/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/AutoConfigurationTest.java index 30e57a9..f06ce38 100644 --- a/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/AutoConfigurationTest.java +++ b/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/AutoConfigurationTest.java @@ -11,6 +11,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; +import org.springframework.lang.NonNull; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.context.bean.override.mockito.MockitoSpyBean; @@ -22,6 +23,7 @@ import io.javaoperatorsdk.operator.api.config.ControllerConfiguration; import io.javaoperatorsdk.operator.api.reconciler.Reconciler; import io.javaoperatorsdk.operator.processing.retry.GenericRetry; +import io.javaoperatorsdk.operator.springboot.starter.properties.OperatorConfigurationProperties; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.atIndex; @@ -50,19 +52,16 @@ public class AutoConfigurationTest { @Autowired private List> reconcilers; - @Autowired - private Consumer compositeConfigurationServiceOverrider; - @MockitoBean private Cloner cloner; @Test public void loadsKubernetesClientPropertiesProperly() { final var operatorProperties = config.getClient(); - assertEquals("user", operatorProperties.getUsername().get()); - assertEquals("password", operatorProperties.getPassword().get()); - assertEquals("token", operatorProperties.getOauthToken().get()); - assertEquals("http://master.url", operatorProperties.getMasterUrl().get()); + assertEquals("user", operatorProperties.getUsername().orElseThrow()); + assertEquals("password", operatorProperties.getPassword().orElseThrow()); + assertEquals("token", operatorProperties.getOauthToken().orElseThrow()); + assertEquals("http://master.url", operatorProperties.getMasterUrl().orElseThrow()); } @Test @@ -78,13 +77,12 @@ public void loadsRetryPropertiesProperly() { @Test public void beansCreated() { assertNotNull(kubernetesClient); - assertNotNull(compositeConfigurationServiceOverrider); } @Test public void reconcilersAreDiscovered() { assertEquals(1, reconcilers.size()); - assertTrue(reconcilers.get(0) instanceof TestReconciler); + assertInstanceOf(TestReconciler.class, reconcilers.get(0)); } @Test @@ -120,7 +118,7 @@ static class TestConfig { public BeanPostProcessor operatorPostProcessor() { return new BeanPostProcessor() { @Override - public Object postProcessAfterInitialization(Object bean, String beanName) + public Object postProcessAfterInitialization(@NonNull Object bean, @NonNull String beanName) throws BeansException { if (bean instanceof Operator operator) { doNothing().when(operator).start(); diff --git a/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/CRDApplierTest.java b/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/CRDApplierTest.java index 3feb998..5cb1525 100644 --- a/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/CRDApplierTest.java +++ b/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/CRDApplierTest.java @@ -11,9 +11,12 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.dsl.NamespaceListVisitFromServerGetDeleteRecreateWaitApplicable; -import io.javaoperatorsdk.operator.springboot.starter.CRDApplier.CRDTransformer; -import io.javaoperatorsdk.operator.springboot.starter.CRDApplier.DefaultCRDApplier; +import io.javaoperatorsdk.operator.springboot.starter.crd.CRDApplier; +import io.javaoperatorsdk.operator.springboot.starter.crd.CRDTransformer; +import io.javaoperatorsdk.operator.springboot.starter.crd.DefaultCRDApplier; import io.javaoperatorsdk.operator.springboot.starter.model.TestResource; +import io.javaoperatorsdk.operator.springboot.starter.properties.CrdProperties; +import io.javaoperatorsdk.operator.springboot.starter.properties.OperatorConfigurationProperties; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -35,7 +38,12 @@ class CRDApplierTest { private String crdPath = "/META-INF/fabric8/"; private CRDApplier applier() { - return new DefaultCRDApplier(kubernetesClient, crdTransformers, crdPath, crdSuffix); + var props = new OperatorConfigurationProperties(); + var crd = new CrdProperties(); + crd.setPath(crdPath); + crd.setSuffix(crdSuffix); + props.setCrd(crd); + return new DefaultCRDApplier(kubernetesClient, props, crdTransformers); } @Test diff --git a/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/CrdApplierConfigurationTest.java b/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/CrdApplierConfigurationTest.java index e247b19..23f680c 100644 --- a/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/CrdApplierConfigurationTest.java +++ b/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/CrdApplierConfigurationTest.java @@ -4,6 +4,7 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner; import io.javaoperatorsdk.operator.Operator; +import io.javaoperatorsdk.operator.springboot.starter.crd.DefaultCRDApplier; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -12,12 +13,13 @@ public class CrdApplierConfigurationTest { private static final ApplicationContextRunner runner = new ApplicationContextRunner() .withUserConfiguration(OperatorAutoConfiguration.class) + .withBean(DefaultCRDApplier.class) .withBean(Operator.class, () -> mock(Operator.class)); @Test void shouldNotCreateByDefault() { runner.run(ctx -> assertThat(ctx) - .doesNotHaveBean("crdApplier") + .doesNotHaveBean("defaultCRDApplier") .hasBean("disabledCrdApplier")); } @@ -25,7 +27,7 @@ void shouldNotCreateByDefault() { void shouldNotCreateWhenDisabled() { runner.withPropertyValues("javaoperatorsdk.crd.apply-on-startup=false") .run(ctx -> assertThat(ctx) - .doesNotHaveBean("crdApplier") + .doesNotHaveBean("defaultCRDApplier") .hasBean("disabledCrdApplier")); } @@ -33,7 +35,7 @@ void shouldNotCreateWhenDisabled() { void shouldCreateWhenEnabled() { runner.withPropertyValues("javaoperatorsdk.crd.apply-on-startup=true") .run(ctx -> assertThat(ctx) - .hasBean("crdApplier") + .hasBean("defaultCRDApplier") .doesNotHaveBean("disabledCrdApplier")); } } diff --git a/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/OperatorStarterTest.java b/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/OperatorStarterTest.java index b79b007..25d8b6b 100644 --- a/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/OperatorStarterTest.java +++ b/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/OperatorStarterTest.java @@ -13,11 +13,7 @@ import io.javaoperatorsdk.operator.Operator; import io.javaoperatorsdk.operator.RegisteredController; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) public class OperatorStarterTest { @@ -25,7 +21,7 @@ public class OperatorStarterTest { @Mock private Operator operator; @Mock - private CRDApplier CRDApplier; + private io.javaoperatorsdk.operator.springboot.starter.crd.CRDApplier CRDApplier; @Mock private ApplicationReadyEvent event; @Mock diff --git a/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/dependentresourceconfigurator/ConfigMapDRConfigurator.java b/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/dependentresourceconfigurator/ConfigMapDRConfigurator.java new file mode 100644 index 0000000..551a9e6 --- /dev/null +++ b/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/dependentresourceconfigurator/ConfigMapDRConfigurator.java @@ -0,0 +1,21 @@ +package io.javaoperatorsdk.operator.springboot.starter.dependentresourceconfigurator; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfig; +import io.javaoperatorsdk.operator.springboot.starter.DependentResourceConfigurator; + +public class ConfigMapDRConfigurator extends KubernetesDependentResourceConfig + implements DependentResourceConfigurator { + + public ConfigMapDRConfigurator() { + super(null, true, InformerConfiguration.builder(ConfigMap.class).build()); + } + + @Override + public String getName() { + return DependentResourceConfigMap.NAME; + } + + public void getsCalled() {} +} diff --git a/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/dependentresourceconfigurator/DependentResourceConfigMap.java b/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/dependentresourceconfigurator/DependentResourceConfigMap.java new file mode 100644 index 0000000..7d3a434 --- /dev/null +++ b/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/dependentresourceconfigurator/DependentResourceConfigMap.java @@ -0,0 +1,23 @@ +package io.javaoperatorsdk.operator.springboot.starter.dependentresourceconfigurator; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfig; +import io.javaoperatorsdk.operator.springboot.starter.model.TestResource; + +public class DependentResourceConfigMap + extends CRUDKubernetesDependentResource { + + public static final String NAME = "configMapDR"; + + public DependentResourceConfigMap() { + super(ConfigMap.class, NAME); + } + + @Override + public void configureWith(KubernetesDependentResourceConfig config) { + if (config instanceof ConfigMapDRConfigurator drc) { + drc.getsCalled(); + } + } +} diff --git a/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/dependentresourceconfigurator/DependentResourceConfiguratorIntegrationTest.java b/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/dependentresourceconfigurator/DependentResourceConfiguratorIntegrationTest.java new file mode 100644 index 0000000..9a89dd4 --- /dev/null +++ b/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/dependentresourceconfigurator/DependentResourceConfiguratorIntegrationTest.java @@ -0,0 +1,55 @@ +package io.javaoperatorsdk.operator.springboot.starter.dependentresourceconfigurator; + +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.bean.override.mockito.MockitoSpyBean; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import io.javaoperatorsdk.jenvtest.junit.EnableKubeAPIServer; +import io.javaoperatorsdk.operator.api.reconciler.Reconciler; +import io.javaoperatorsdk.operator.springboot.starter.crd.CRDTransformer; +import io.javaoperatorsdk.operator.springboot.starter.model.TestResource; + +import static org.mockito.Mockito.times; + +@EnableKubeAPIServer(updateKubeConfigFile = true) +@SpringBootTest(properties = { + "javaoperatorsdk.crd.apply-on-startup = true", + "javaoperatorsdk.crd.path = META-INF/fabric8/deeper" +}) +@SpringJUnitConfig +public class DependentResourceConfiguratorIntegrationTest { + + @MockitoSpyBean + ConfigMapDRConfigurator dependentResourceConfigurator; + + @Test + void testLoadingCustomDependentResourceConfigurator() { + Mockito.verify(dependentResourceConfigurator, times(1)).getsCalled(); + } + + @TestConfiguration + public static class TestConfig { + + @Bean + public CRDTransformer transformer() { + return crd -> { + crd.getMetadata().getLabels().put("DRtest", "CRD"); + return crd; + }; + } + + @Bean + public ConfigMapDRConfigurator drConfigurator() { + return new ConfigMapDRConfigurator(); + } + + @Bean + public Reconciler drReconciler() { + return new DependentResourceReconciler(); + } + } +} diff --git a/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/dependentresourceconfigurator/DependentResourceReconciler.java b/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/dependentresourceconfigurator/DependentResourceReconciler.java new file mode 100644 index 0000000..495fb5b --- /dev/null +++ b/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/dependentresourceconfigurator/DependentResourceReconciler.java @@ -0,0 +1,17 @@ +package io.javaoperatorsdk.operator.springboot.starter.dependentresourceconfigurator; + +import io.javaoperatorsdk.operator.api.reconciler.*; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; +import io.javaoperatorsdk.operator.springboot.starter.model.TestResource; + +@Workflow(dependents = @Dependent(type = DependentResourceConfigMap.class, + name = DependentResourceConfigMap.NAME)) +@ControllerConfiguration +public class DependentResourceReconciler implements Reconciler { + + @Override + public UpdateControl reconcile(TestResource resource, + Context context) { + return UpdateControl.noUpdate(); + } +} diff --git a/starter/src/test/resources/META-INF/fabric8/deeper/test-crd-v1.yml b/starter/src/test/resources/META-INF/fabric8/deeper/test-crd-v1.yml index dc4b837..5138b90 100644 --- a/starter/src/test/resources/META-INF/fabric8/deeper/test-crd-v1.yml +++ b/starter/src/test/resources/META-INF/fabric8/deeper/test-crd-v1.yml @@ -37,7 +37,7 @@ spec: served: true storage: false subresources: - status: {} + status: { } - name: v2 schema: openAPIV3Schema: @@ -67,7 +67,7 @@ spec: served: true storage: true subresources: - status: {} + status: { } --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition @@ -108,4 +108,4 @@ spec: served: true storage: true subresources: - status: {} \ No newline at end of file + status: { } \ No newline at end of file diff --git a/starter/src/test/resources/META-INF/fabric8/test-crd-v2.yml b/starter/src/test/resources/META-INF/fabric8/test-crd-v2.yml index 2e81227..cc7cedb 100644 --- a/starter/src/test/resources/META-INF/fabric8/test-crd-v2.yml +++ b/starter/src/test/resources/META-INF/fabric8/test-crd-v2.yml @@ -19,4 +19,4 @@ spec: served: true storage: false subresources: - status: {} \ No newline at end of file + status: { } \ No newline at end of file From 129a2ac0aa4f88d5767400a1150d997c08b2f104 Mon Sep 17 00:00:00 2001 From: 1nval1d Date: Sat, 15 Feb 2025 08:20:47 +0100 Subject: [PATCH 2/3] refactor: remove lombok --- starter/pom.xml | 6 - .../springboot/starter/OperatorStarter.java | 15 +- .../starter/ReconcilerRegistrationUtil.java | 9 +- .../starter/crd/DefaultCRDApplier.java | 12 +- .../starter/properties/CrdProperties.java | 36 +++-- .../KubernetesClientProperties.java | 87 +++++++++-- .../OperatorConfigurationProperties.java | 145 ++++++++++++++++-- .../properties/ReconcilerProperties.java | 107 +++++++++++-- .../starter/properties/RetryProperties.java | 71 +++++++-- 9 files changed, 397 insertions(+), 91 deletions(-) diff --git a/starter/pom.xml b/starter/pom.xml index 8ced795..6df69dc 100644 --- a/starter/pom.xml +++ b/starter/pom.xml @@ -38,12 +38,6 @@ - - org.projectlombok - lombok - 1.18.36 - provided - org.springframework.boot spring-boot-autoconfigure-processor diff --git a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/OperatorStarter.java b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/OperatorStarter.java index 5a572af..5407876 100644 --- a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/OperatorStarter.java +++ b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/OperatorStarter.java @@ -10,19 +10,20 @@ import io.javaoperatorsdk.operator.Operator; import io.javaoperatorsdk.operator.springboot.starter.crd.CRDApplier; -import lombok.AccessLevel; -import lombok.RequiredArgsConstructor; -import lombok.experimental.FieldDefaults; @Component -@RequiredArgsConstructor -@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class OperatorStarter { private static final Logger log = LoggerFactory.getLogger(OperatorStarter.class); - Operator operator; - CRDApplier crdApplier; + private final Operator operator; + private final CRDApplier crdApplier; + + + public OperatorStarter(Operator operator, CRDApplier crdApplier) { + this.operator = operator; + this.crdApplier = crdApplier; + } @EventListener public void start(ApplicationReadyEvent event) { diff --git a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/ReconcilerRegistrationUtil.java b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/ReconcilerRegistrationUtil.java index d61a32d..da4047a 100644 --- a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/ReconcilerRegistrationUtil.java +++ b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/ReconcilerRegistrationUtil.java @@ -10,12 +10,10 @@ import io.javaoperatorsdk.operator.processing.retry.GenericRetry; import io.javaoperatorsdk.operator.springboot.starter.properties.ReconcilerProperties; -import lombok.experimental.UtilityClass; -@UtilityClass public class ReconcilerRegistrationUtil { - public List filterConfigurators(Reconciler reconciler, + public static List filterConfigurators(Reconciler reconciler, Map> configuratorsMap) { var workflow = reconciler.getClass().getAnnotation(Workflow.class); if (workflow == null) @@ -37,8 +35,7 @@ public List filterConfigurators(Reconciler rec return relevant; } - - public void overrideFromProps(ControllerConfigurationOverrider overrider, + public static void overrideFromProps(ControllerConfigurationOverrider overrider, ReconcilerProperties props) { if (props != null) { doIfPresent(props.getFinalizerName(), overrider::withFinalizer); @@ -63,7 +60,7 @@ public void overrideFromProps(ControllerConfigurationOverrider overrider, } } - public void doIfPresent(T prop, Consumer action) { + public static void doIfPresent(T prop, Consumer action) { Optional.ofNullable(prop).ifPresent(action); } } diff --git a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/crd/DefaultCRDApplier.java b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/crd/DefaultCRDApplier.java index 561ae59..a8f2060 100644 --- a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/crd/DefaultCRDApplier.java +++ b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/crd/DefaultCRDApplier.java @@ -15,25 +15,21 @@ import io.fabric8.kubernetes.client.KubernetesClient; import io.javaoperatorsdk.operator.springboot.starter.properties.OperatorConfigurationProperties; -import lombok.AccessLevel; -import lombok.experimental.FieldDefaults; - import static java.util.Arrays.stream; import static java.util.stream.Collectors.joining; import static org.slf4j.LoggerFactory.getLogger; @Component @ConditionalOnProperty(value = "javaoperatorsdk.crd.apply-on-startup", havingValue = "true") -@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class DefaultCRDApplier implements CRDApplier { private static final Logger log = getLogger(DefaultCRDApplier.class); private static final int CRD_READY_WAIT = 2000; - KubernetesClient kubernetesClient; - CRDTransformer crdTransformer; - String crdSuffix; - String crdPath; + private final KubernetesClient kubernetesClient; + private final CRDTransformer crdTransformer; + private final String crdSuffix; + private final String crdPath; public DefaultCRDApplier(KubernetesClient kubernetesClient, OperatorConfigurationProperties configurationProperties, List transformers) { diff --git a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/CrdProperties.java b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/CrdProperties.java index 68a6129..7e4f3ab 100644 --- a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/CrdProperties.java +++ b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/CrdProperties.java @@ -1,20 +1,38 @@ package io.javaoperatorsdk.operator.springboot.starter.properties; -import lombok.AccessLevel; -import lombok.Data; -import lombok.experimental.FieldDefaults; - -@Data -@FieldDefaults(level = AccessLevel.PRIVATE) public class CrdProperties { - boolean applyOnStartup; + private boolean applyOnStartup; /** * path to the resource folder where CRDs are located */ - String path = "/META-INF/fabric8/"; + private String path = "/META-INF/fabric8/"; /** * file suffix to filter out CRDs */ - String suffix = "-v1.yml"; + private String suffix = "-v1.yml"; + + public boolean isApplyOnStartup() { + return applyOnStartup; + } + + public void setApplyOnStartup(boolean applyOnStartup) { + this.applyOnStartup = applyOnStartup; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public String getSuffix() { + return suffix; + } + + public void setSuffix(String suffix) { + this.suffix = suffix; + } } diff --git a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/KubernetesClientProperties.java b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/KubernetesClientProperties.java index ed24e8d..077e691 100644 --- a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/KubernetesClientProperties.java +++ b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/KubernetesClientProperties.java @@ -1,42 +1,99 @@ package io.javaoperatorsdk.operator.springboot.starter.properties; +import java.util.Objects; import java.util.Optional; -import lombok.AccessLevel; -import lombok.Data; -import lombok.experimental.Accessors; -import lombok.experimental.FieldDefaults; - -@Data -@Accessors(chain = true) -@FieldDefaults(level = AccessLevel.PRIVATE) public class KubernetesClientProperties { - boolean openshift = false; - String context; - String username; - String password; - String oauthToken; - String masterUrl; - boolean trustSelfSignedCertificates = false; + private boolean openshift = false; + private String context; + private String username; + private String password; + private String oauthToken; + private String masterUrl; + private boolean trustSelfSignedCertificates = false; + + + public boolean isOpenshift() { + return openshift; + } + + public KubernetesClientProperties setOpenshift(boolean openshift) { + this.openshift = openshift; + return this; + } + + public boolean isTrustSelfSignedCertificates() { + return trustSelfSignedCertificates; + } + + public KubernetesClientProperties setTrustSelfSignedCertificates( + boolean trustSelfSignedCertificates) { + this.trustSelfSignedCertificates = trustSelfSignedCertificates; + return this; + } public Optional getContext() { return Optional.ofNullable(context); } + public KubernetesClientProperties setContext(String context) { + this.context = context; + return this; + } + public Optional getUsername() { return Optional.ofNullable(username); } + public KubernetesClientProperties setUsername(String username) { + this.username = username; + return this; + } + public Optional getPassword() { return Optional.ofNullable(password); } + public KubernetesClientProperties setPassword(String password) { + this.password = password; + return this; + } + public Optional getOauthToken() { return Optional.ofNullable(oauthToken); } + public KubernetesClientProperties setOauthToken(String oauthToken) { + this.oauthToken = oauthToken; + return this; + } + public Optional getMasterUrl() { return Optional.ofNullable(masterUrl); } + + public KubernetesClientProperties setMasterUrl(String masterUrl) { + this.masterUrl = masterUrl; + return this; + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof KubernetesClientProperties that)) + return false; + return isOpenshift() == that.isOpenshift() + && isTrustSelfSignedCertificates() == that.isTrustSelfSignedCertificates() + && Objects.equals(getContext(), that.getContext()) + && Objects.equals(getUsername(), that.getUsername()) + && Objects.equals(getPassword(), that.getPassword()) + && Objects.equals(getOauthToken(), that.getOauthToken()) + && Objects.equals(getMasterUrl(), that.getMasterUrl()); + } + + @Override + public int hashCode() { + return Objects.hash(isOpenshift(), getContext(), getUsername(), getPassword(), getOauthToken(), + getMasterUrl(), isTrustSelfSignedCertificates()); + } } diff --git a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/OperatorConfigurationProperties.java b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/OperatorConfigurationProperties.java index f72049f..5b18a65 100644 --- a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/OperatorConfigurationProperties.java +++ b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/OperatorConfigurationProperties.java @@ -3,30 +3,145 @@ import java.time.Duration; import java.util.Collections; import java.util.Map; +import java.util.Objects; import org.springframework.boot.context.properties.ConfigurationProperties; import io.javaoperatorsdk.operator.api.config.ConfigurationService; -import lombok.AccessLevel; -import lombok.Data; -import lombok.experimental.FieldDefaults; - -@Data -@FieldDefaults(level = AccessLevel.PRIVATE) @ConfigurationProperties(prefix = "javaoperatorsdk") public class OperatorConfigurationProperties { CrdProperties crd = new CrdProperties(); KubernetesClientProperties client = new KubernetesClientProperties(); - Map reconcilers = Collections.emptyMap(); - boolean checkCrdAndValidateLocalModel = true; - int concurrentReconciliationThreads = ConfigurationService.DEFAULT_RECONCILIATION_THREADS_NUMBER; - Integer minConcurrentReconciliationThreads; - Integer concurrentWorkflowExecutorThreads; - Integer minConcurrentWorkflowExecutorThreads; - Boolean closeClientOnStop; - Boolean stopOnInformerErrorDuringStartup; - Duration cacheSyncTimeout; + private Map reconcilers = Collections.emptyMap(); + private boolean checkCrdAndValidateLocalModel = true; + private int concurrentReconciliationThreads = + ConfigurationService.DEFAULT_RECONCILIATION_THREADS_NUMBER; + private Integer minConcurrentReconciliationThreads; + private Integer concurrentWorkflowExecutorThreads; + private Integer minConcurrentWorkflowExecutorThreads; + private Boolean closeClientOnStop; + private Boolean stopOnInformerErrorDuringStartup; + private Duration cacheSyncTimeout; + + public CrdProperties getCrd() { + return crd; + } + + public void setCrd(CrdProperties crd) { + this.crd = crd; + } + + public KubernetesClientProperties getClient() { + return client; + } + + public void setClient(KubernetesClientProperties client) { + this.client = client; + } + + public Map getReconcilers() { + return reconcilers; + } + + public void setReconcilers(Map reconcilers) { + this.reconcilers = reconcilers; + } + + public boolean isCheckCrdAndValidateLocalModel() { + return checkCrdAndValidateLocalModel; + } + + public void setCheckCrdAndValidateLocalModel(boolean checkCrdAndValidateLocalModel) { + this.checkCrdAndValidateLocalModel = checkCrdAndValidateLocalModel; + } + + public int getConcurrentReconciliationThreads() { + return concurrentReconciliationThreads; + } + + public void setConcurrentReconciliationThreads(int concurrentReconciliationThreads) { + this.concurrentReconciliationThreads = concurrentReconciliationThreads; + } + + public Integer getMinConcurrentReconciliationThreads() { + return minConcurrentReconciliationThreads; + } + + public void setMinConcurrentReconciliationThreads(Integer minConcurrentReconciliationThreads) { + this.minConcurrentReconciliationThreads = minConcurrentReconciliationThreads; + } + + public Integer getConcurrentWorkflowExecutorThreads() { + return concurrentWorkflowExecutorThreads; + } + + public void setConcurrentWorkflowExecutorThreads(Integer concurrentWorkflowExecutorThreads) { + this.concurrentWorkflowExecutorThreads = concurrentWorkflowExecutorThreads; + } + + public Integer getMinConcurrentWorkflowExecutorThreads() { + return minConcurrentWorkflowExecutorThreads; + } + + public void setMinConcurrentWorkflowExecutorThreads( + Integer minConcurrentWorkflowExecutorThreads) { + this.minConcurrentWorkflowExecutorThreads = minConcurrentWorkflowExecutorThreads; + } + + public Boolean getCloseClientOnStop() { + return closeClientOnStop; + } + + public void setCloseClientOnStop(Boolean closeClientOnStop) { + this.closeClientOnStop = closeClientOnStop; + } + + public Boolean getStopOnInformerErrorDuringStartup() { + return stopOnInformerErrorDuringStartup; + } + + public void setStopOnInformerErrorDuringStartup(Boolean stopOnInformerErrorDuringStartup) { + this.stopOnInformerErrorDuringStartup = stopOnInformerErrorDuringStartup; + } + + public Duration getCacheSyncTimeout() { + return cacheSyncTimeout; + } + + public void setCacheSyncTimeout(Duration cacheSyncTimeout) { + this.cacheSyncTimeout = cacheSyncTimeout; + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof OperatorConfigurationProperties that)) + return false; + return isCheckCrdAndValidateLocalModel() == that.isCheckCrdAndValidateLocalModel() + && getConcurrentReconciliationThreads() == that.getConcurrentReconciliationThreads() + && Objects.equals(getCrd(), that.getCrd()) + && Objects.equals(getClient(), that.getClient()) + && Objects.equals(getReconcilers(), that.getReconcilers()) + && Objects.equals(getMinConcurrentReconciliationThreads(), + that.getMinConcurrentReconciliationThreads()) + && Objects.equals(getConcurrentWorkflowExecutorThreads(), + that.getConcurrentWorkflowExecutorThreads()) + && Objects.equals(getMinConcurrentWorkflowExecutorThreads(), + that.getMinConcurrentWorkflowExecutorThreads()) + && Objects.equals(getCloseClientOnStop(), that.getCloseClientOnStop()) + && Objects.equals(getStopOnInformerErrorDuringStartup(), + that.getStopOnInformerErrorDuringStartup()) + && Objects.equals(getCacheSyncTimeout(), that.getCacheSyncTimeout()); + } + + @Override + public int hashCode() { + return Objects.hash(getCrd(), getClient(), getReconcilers(), isCheckCrdAndValidateLocalModel(), + getConcurrentReconciliationThreads(), getMinConcurrentReconciliationThreads(), + getConcurrentWorkflowExecutorThreads(), + getMinConcurrentWorkflowExecutorThreads(), getCloseClientOnStop(), + getStopOnInformerErrorDuringStartup(), getCacheSyncTimeout()); + } } diff --git a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/ReconcilerProperties.java b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/ReconcilerProperties.java index 4b90c01..e49830b 100644 --- a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/ReconcilerProperties.java +++ b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/ReconcilerProperties.java @@ -1,21 +1,102 @@ package io.javaoperatorsdk.operator.springboot.starter.properties; + import java.time.Duration; +import java.util.Objects; import java.util.Set; -import lombok.AccessLevel; -import lombok.Data; -import lombok.experimental.FieldDefaults; -@Data -@FieldDefaults(level = AccessLevel.PRIVATE) public class ReconcilerProperties { - String name; - String finalizerName; - Boolean generationAware; - Boolean clusterScoped; - Set namespaces; - RetryProperties retry; - String labelSelector; - Duration reconciliationMaxInterval; + private String name; + private String finalizerName; + private Boolean generationAware; + private Boolean clusterScoped; + private Set namespaces; + private RetryProperties retry; + private String labelSelector; + private Duration reconciliationMaxInterval; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getFinalizerName() { + return finalizerName; + } + + public void setFinalizerName(String finalizerName) { + this.finalizerName = finalizerName; + } + + public Boolean getGenerationAware() { + return generationAware; + } + + public void setGenerationAware(Boolean generationAware) { + this.generationAware = generationAware; + } + + public Boolean getClusterScoped() { + return clusterScoped; + } + + public void setClusterScoped(Boolean clusterScoped) { + this.clusterScoped = clusterScoped; + } + + public Set getNamespaces() { + return namespaces; + } + + public void setNamespaces(Set namespaces) { + this.namespaces = namespaces; + } + + public RetryProperties getRetry() { + return retry; + } + + public void setRetry(RetryProperties retry) { + this.retry = retry; + } + + public String getLabelSelector() { + return labelSelector; + } + + public void setLabelSelector(String labelSelector) { + this.labelSelector = labelSelector; + } + + public Duration getReconciliationMaxInterval() { + return reconciliationMaxInterval; + } + + public void setReconciliationMaxInterval(Duration reconciliationMaxInterval) { + this.reconciliationMaxInterval = reconciliationMaxInterval; + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof ReconcilerProperties that)) + return false; + return Objects.equals(getName(), that.getName()) + && Objects.equals(getFinalizerName(), that.getFinalizerName()) + && Objects.equals(getGenerationAware(), that.getGenerationAware()) + && Objects.equals(getClusterScoped(), that.getClusterScoped()) + && Objects.equals(getNamespaces(), that.getNamespaces()) + && Objects.equals(getRetry(), that.getRetry()) + && Objects.equals(getLabelSelector(), that.getLabelSelector()) + && Objects.equals(getReconciliationMaxInterval(), that.getReconciliationMaxInterval()); + } + + @Override + public int hashCode() { + return Objects.hash(getName(), getFinalizerName(), getGenerationAware(), getClusterScoped(), + getNamespaces(), getRetry(), getLabelSelector(), getReconciliationMaxInterval()); + } } diff --git a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/RetryProperties.java b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/RetryProperties.java index e05d749..a76da4b 100644 --- a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/RetryProperties.java +++ b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/properties/RetryProperties.java @@ -1,17 +1,64 @@ package io.javaoperatorsdk.operator.springboot.starter.properties; -import lombok.AccessLevel; -import lombok.Data; -import lombok.experimental.Accessors; -import lombok.experimental.FieldDefaults; - -@Data -@Accessors(chain = true) -@FieldDefaults(level = AccessLevel.PRIVATE) + +import java.util.Objects; + public class RetryProperties { - Integer maxAttempts; - Long initialInterval; - Double intervalMultiplier; - Long maxInterval; + private Integer maxAttempts; + private Long initialInterval; + private Double intervalMultiplier; + private Long maxInterval; + + public Integer getMaxAttempts() { + return maxAttempts; + } + + public RetryProperties setMaxAttempts(Integer maxAttempts) { + this.maxAttempts = maxAttempts; + return this; + } + + public Long getInitialInterval() { + return initialInterval; + } + + public RetryProperties setInitialInterval(Long initialInterval) { + this.initialInterval = initialInterval; + return this; + } + + public Double getIntervalMultiplier() { + return intervalMultiplier; + } + + public RetryProperties setIntervalMultiplier(Double intervalMultiplier) { + this.intervalMultiplier = intervalMultiplier; + return this; + } + + public Long getMaxInterval() { + return maxInterval; + } + + public RetryProperties setMaxInterval(Long maxInterval) { + this.maxInterval = maxInterval; + return this; + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof RetryProperties that)) + return false; + return Objects.equals(getMaxAttempts(), that.getMaxAttempts()) + && Objects.equals(getInitialInterval(), that.getInitialInterval()) + && Objects.equals(getIntervalMultiplier(), that.getIntervalMultiplier()) + && Objects.equals(getMaxInterval(), that.getMaxInterval()); + } + + @Override + public int hashCode() { + return Objects.hash(getMaxAttempts(), getInitialInterval(), getIntervalMultiplier(), + getMaxInterval()); + } } From c0bb7c8b1718de3a1efc5de7cbf715a00bc978fa Mon Sep 17 00:00:00 2001 From: 1nval1d Date: Sat, 15 Feb 2025 08:47:18 +0100 Subject: [PATCH 3/3] refactor: add unit tests --- .../starter/ReconcilerRegistrationUtil.java | 4 +- .../springboot/starter/CRDApplierTest.java | 2 +- .../ReconcilerRegistrationUtilTest.java | 131 ++++++++++++++++++ 3 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/ReconcilerRegistrationUtilTest.java diff --git a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/ReconcilerRegistrationUtil.java b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/ReconcilerRegistrationUtil.java index da4047a..b93f47f 100644 --- a/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/ReconcilerRegistrationUtil.java +++ b/starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/ReconcilerRegistrationUtil.java @@ -16,8 +16,10 @@ public class ReconcilerRegistrationUtil { public static List filterConfigurators(Reconciler reconciler, Map> configuratorsMap) { var workflow = reconciler.getClass().getAnnotation(Workflow.class); - if (workflow == null) + if (workflow == null) { return Collections.emptyList(); + } + var dependents = workflow.dependents(); ArrayList relevant = new ArrayList<>(); for (Dependent dependent : dependents) { diff --git a/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/CRDApplierTest.java b/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/CRDApplierTest.java index 5cb1525..ef11541 100644 --- a/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/CRDApplierTest.java +++ b/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/CRDApplierTest.java @@ -35,7 +35,7 @@ class CRDApplierTest { @Mock private NamespaceListVisitFromServerGetDeleteRecreateWaitApplicable loadedResource; private String crdSuffix = "-v2.yml"; - private String crdPath = "/META-INF/fabric8/"; + private final String crdPath = "/META-INF/fabric8/"; private CRDApplier applier() { var props = new OperatorConfigurationProperties(); diff --git a/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/ReconcilerRegistrationUtilTest.java b/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/ReconcilerRegistrationUtilTest.java new file mode 100644 index 0000000..4f9d5cb --- /dev/null +++ b/starter/src/test/java/io/javaoperatorsdk/operator/springboot/starter/ReconcilerRegistrationUtilTest.java @@ -0,0 +1,131 @@ +package io.javaoperatorsdk.operator.springboot.starter; + +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.Reconciler; +import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; +import io.javaoperatorsdk.operator.api.reconciler.Workflow; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringJUnitConfig +class ReconcilerRegistrationUtilTest { + + Map> configurators = Map.of( + "SingleEntry", List.of(() -> "SingleEntry"), + "SecondEntry", List.of(() -> "SecondEntry"), + "DoubleEntry", List.of(() -> "DoubleEntry", () -> "DoubleEntry")); + + @Test + void testFilterConfiguratorsNoWorkflow() { + Reconciler test = (resource, context) -> null; + + List result = assertDoesNotThrow( + () -> ReconcilerRegistrationUtil.filterConfigurators(test, configurators)); + assertThat(result).isNotNull().isEmpty(); + } + + @Test + void testFilterConfiguratorsWithEmptyWorkflow() { + Reconciler test = new TestEmptyReconciler(); + + List result = assertDoesNotThrow( + () -> ReconcilerRegistrationUtil.filterConfigurators(test, configurators)); + assertThat(result).isNotNull().isEmpty(); + } + + @Test + void testFilterConfiguratorsWithSingleEntryWorkflow() { + Reconciler test = new TestSingleEntryReconciler(); + + List result = assertDoesNotThrow( + () -> ReconcilerRegistrationUtil.filterConfigurators(test, configurators)); + assertThat(result) + .isNotNull() + .hasSize(1) + .element(0) + .matches(e -> "SingleEntry".equals(e.getName())); + } + + @Test + void testFilterConfiguratorsWithSecondEntryWorkflow() { + Reconciler test = new TestSecondEntryReconciler(); + + List result = assertDoesNotThrow( + () -> ReconcilerRegistrationUtil.filterConfigurators(test, configurators)); + assertThat(result) + .isNotNull() + .hasSize(2) + .anyMatch(e -> "SingleEntry".equals(e.getName())) + .anyMatch(e -> "SecondEntry".equals(e.getName())); + } + + @Test + void testFilterConfiguratorsWithDoubleEntryWorkflow() { + Reconciler test = new TestDoubleEntryReconciler(); + + Throwable th = assertThrows(IllegalStateException.class, + () -> ReconcilerRegistrationUtil.filterConfigurators(test, configurators)); + assertThat(th) + .isNotNull() + .hasMessageContaining("more than one config for Dependent Resource") + .hasMessageContaining("DoubleEntry"); + } + + @Workflow(dependents = {}) + private static class TestEmptyReconciler implements Reconciler { + + @Override + public UpdateControl reconcile(HasMetadata resource, + Context context) { + return null; + } + } + + @Workflow(dependents = { + @Dependent(name = "SingleEntry", type = DependentResource.class) + }) + private static class TestSingleEntryReconciler implements Reconciler { + + @Override + public UpdateControl reconcile(HasMetadata resource, + Context context) { + return null; + } + } + + @Workflow(dependents = { + @Dependent(name = "SingleEntry", type = DependentResource.class), + @Dependent(name = "SecondEntry", type = DependentResource.class) + }) + private static class TestSecondEntryReconciler implements Reconciler { + + @Override + public UpdateControl reconcile(HasMetadata resource, + Context context) { + return null; + } + } + + @Workflow(dependents = { + @Dependent(name = "DoubleEntry", type = DependentResource.class) + }) + private static class TestDoubleEntryReconciler implements Reconciler { + + @Override + public UpdateControl reconcile(HasMetadata resource, + Context context) { + return null; + } + } +}