diff --git a/operator-framework-core/pom.xml b/operator-framework-core/pom.xml index e46f893d8d..43d3f95758 100644 --- a/operator-framework-core/pom.xml +++ b/operator-framework-core/pom.xml @@ -2,72 +2,97 @@ <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - <modelVersion>4.0.0</modelVersion> - <parent> - <groupId>io.javaoperatorsdk</groupId> - <artifactId>java-operator-sdk</artifactId> - <version>1.7.1-SNAPSHOT</version> - <relativePath>../pom.xml</relativePath> - </parent> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>io.javaoperatorsdk</groupId> + <artifactId>java-operator-sdk</artifactId> + <version>1.7.1-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> - <artifactId>operator-framework-core</artifactId> - <name>Operator SDK - Framework - Core</name> - <description>Core framework for implementing Kubernetes operators</description> - <packaging>jar</packaging> + <artifactId>operator-framework-core</artifactId> + <name>Operator SDK - Framework - Core</name> + <description>Core framework for implementing Kubernetes operators</description> + <packaging>jar</packaging> - <properties> - <java.version>11</java.version> - <maven.compiler.source>11</maven.compiler.source> - <maven.compiler.target>11</maven.compiler.target> - </properties> + <properties> + <java.version>11</java.version> + <maven.compiler.source>11</maven.compiler.source> + <maven.compiler.target>11</maven.compiler.target> + </properties> - <build> - <plugins> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-surefire-plugin</artifactId> - <version>${surefire.version}</version> - </plugin> - </plugins> - </build> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <version>${surefire.version}</version> + </plugin> + <plugin> + <!-- Used to generate the version / commit information --> + <groupId>pl.project13.maven</groupId> + <artifactId>git-commit-id-plugin</artifactId> + <version>4.0.3</version> + <executions> + <execution> + <id>get-the-git-infos</id> + <goals> + <goal>revision</goal> + </goals> + <phase>initialize</phase> + </execution> + </executions> + <configuration> + <generateGitPropertiesFile>true</generateGitPropertiesFile> + <generateGitPropertiesFilename>${project.build.outputDirectory}/version.properties + </generateGitPropertiesFilename> + <includeOnlyProperties> + <includeOnlyProperty>^git.build.(time|version)$</includeOnlyProperty> + <includeOnlyProperty>^git.commit.id.(abbrev|full)$</includeOnlyProperty> + </includeOnlyProperties> + <commitIdGenerationMode>full</commitIdGenerationMode> + </configuration> + </plugin> + </plugins> + </build> - <dependencies> - <dependency> - <groupId>io.fabric8</groupId> - <artifactId>openshift-client</artifactId> - </dependency> - <dependency> - <groupId>org.slf4j</groupId> - <artifactId>slf4j-api</artifactId> - </dependency> + <dependencies> + <dependency> + <groupId>io.fabric8</groupId> + <artifactId>openshift-client</artifactId> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </dependency> - <dependency> - <groupId>org.junit.jupiter</groupId> - <artifactId>junit-jupiter-api</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.junit.jupiter</groupId> - <artifactId>junit-jupiter-engine</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.mockito</groupId> - <artifactId>mockito-core</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.apache.logging.log4j</groupId> - <artifactId>log4j-slf4j-impl</artifactId> - <version>2.13.3</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.assertj</groupId> - <artifactId>assertj-core</artifactId> - <version>3.18.0</version> - <scope>test</scope> - </dependency> - </dependencies> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-slf4j-impl</artifactId> + <version>2.13.3</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <version>3.18.0</version> + <scope>test</scope> + </dependency> + </dependencies> </project> diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ControllerUtils.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ControllerUtils.java index 287312d0e6..55cbe9d6f6 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ControllerUtils.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ControllerUtils.java @@ -1,6 +1,5 @@ package io.javaoperatorsdk.operator; -import io.fabric8.kubernetes.client.CustomResource; import io.javaoperatorsdk.operator.api.Controller; import io.javaoperatorsdk.operator.api.ResourceController; import java.util.Locale; @@ -13,11 +12,6 @@ public static String getDefaultFinalizerName(String crdName) { return crdName + FINALIZER_NAME_SUFFIX; } - public static boolean hasGivenFinalizer(CustomResource resource, String finalizer) { - return resource.getMetadata().getFinalizers() != null - && resource.getMetadata().getFinalizers().contains(finalizer); - } - public static String getNameFor(Class<? extends ResourceController> controllerClass) { // if the controller annotation has a name attribute, use it final var annotation = controllerClass.getAnnotation(Controller.class); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java index b1e8f25af5..5045eeddcf 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java @@ -12,7 +12,6 @@ import io.javaoperatorsdk.operator.processing.event.DefaultEventSourceManager; import io.javaoperatorsdk.operator.processing.event.internal.CustomResourceEventSource; import io.javaoperatorsdk.operator.processing.retry.GenericRetry; -import io.javaoperatorsdk.operator.processing.retry.Retry; import java.util.Arrays; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,11 +28,44 @@ public Operator(KubernetesClient k8sClient, ConfigurationService configurationSe this.configurationService = configurationService; } + /** + * Finishes the operator startup process. This is mostly used in injection-aware applications + * where there is no obvious entrypoint to the application which can trigger the injection process + * and start the cluster monitoring processes. + */ + public void start() { + final var version = configurationService.getVersion(); + log.info( + "Operator {} (commit: {}) built on {} starting...", + version.getSdkVersion(), + version.getCommit(), + version.getBuiltTime()); + } + + /** + * Registers the specified controller with this operator. + * + * @param controller the controller to register + * @param <R> the {@code CustomResource} type associated with the controller + * @throws OperatorException if a problem occurred during the registration process + */ public <R extends CustomResource> void register(ResourceController<R> controller) throws OperatorException { register(controller, null); } + /** + * Registers the specified controller with this operator, overriding its default configuration by + * the specified one (usually created via {@link + * io.javaoperatorsdk.operator.api.config.ControllerConfigurationOverrider#override(ControllerConfiguration)}, + * passing it the controller's original configuration. + * + * @param controller the controller to register + * @param configuration the configuration with which we want to register the controller, if {@code + * null}, the controller's orginal configuration is used + * @param <R> the {@code CustomResource} type associated with the controller + * @throws OperatorException if a problem occurred during the registration process + */ public <R extends CustomResource> void register( ResourceController<R> controller, ControllerConfiguration<R> configuration) throws OperatorException { @@ -49,55 +81,57 @@ public <R extends CustomResource> void register( if (configuration == null) { configuration = existing; } + final var retry = GenericRetry.fromConfiguration(configuration.getRetryConfiguration()); final var targetNamespaces = configuration.getNamespaces().toArray(new String[] {}); - registerController(controller, configuration.watchAllNamespaces(), retry, targetNamespaces); - } - } + Class<R> resClass = configuration.getCustomResourceClass(); + String finalizer = configuration.getFinalizer(); + final var client = k8sClient.customResources(resClass); + EventDispatcher dispatcher = + new EventDispatcher( + controller, finalizer, new EventDispatcher.CustomResourceFacade(client)); - @SuppressWarnings("rawtypes") - private <R extends CustomResource> void registerController( - ResourceController<R> controller, - boolean watchAllNamespaces, - Retry retry, - String... targetNamespaces) - throws OperatorException { - final var configuration = configurationService.getConfigurationFor(controller); - Class<R> resClass = configuration.getCustomResourceClass(); - String finalizer = configuration.getFinalizer(); - MixedOperation client = k8sClient.customResources(resClass); - EventDispatcher eventDispatcher = - new EventDispatcher( - controller, finalizer, new EventDispatcher.CustomResourceFacade(client)); + // check that the custom resource is known by the cluster + final var crdName = configuration.getCRDName(); + final var crd = + k8sClient.apiextensions().v1().customResourceDefinitions().withName(crdName).get(); + final var controllerName = configuration.getName(); + if (crd == null) { + throw new OperatorException( + "'" + + crdName + + "' CRD was not found on the cluster, controller " + + controllerName + + " cannot be registered"); + } - CustomResourceCache customResourceCache = new CustomResourceCache(); - DefaultEventHandler defaultEventHandler = - new DefaultEventHandler( - customResourceCache, eventDispatcher, controller.getClass().getName(), retry); - DefaultEventSourceManager eventSourceManager = - new DefaultEventSourceManager(defaultEventHandler, retry != null); - defaultEventHandler.setEventSourceManager(eventSourceManager); - eventDispatcher.setEventSourceManager(eventSourceManager); + CustomResourceCache customResourceCache = new CustomResourceCache(); + DefaultEventHandler defaultEventHandler = + new DefaultEventHandler(customResourceCache, dispatcher, controllerName, retry); + DefaultEventSourceManager eventSourceManager = + new DefaultEventSourceManager(defaultEventHandler, retry != null); + defaultEventHandler.setEventSourceManager(eventSourceManager); + dispatcher.setEventSourceManager(eventSourceManager); - controller.init(eventSourceManager); - CustomResourceEventSource customResourceEventSource = - createCustomResourceEventSource( - client, - customResourceCache, - watchAllNamespaces, - targetNamespaces, - defaultEventHandler, - configuration.isGenerationAware(), - finalizer); - eventSourceManager.registerCustomResourceEventSource(customResourceEventSource); + controller.init(eventSourceManager); + final boolean watchAllNamespaces = configuration.watchAllNamespaces(); + CustomResourceEventSource customResourceEventSource = + createCustomResourceEventSource( + client, + customResourceCache, + watchAllNamespaces, + targetNamespaces, + defaultEventHandler, + configuration.isGenerationAware(), + finalizer); + eventSourceManager.registerCustomResourceEventSource(customResourceEventSource); - log.info( - "Registered Controller: '{}' for CRD: '{}' for namespaces: {}", - controller.getClass().getSimpleName(), - resClass, - targetNamespaces.length == 0 - ? "[all/client namespace]" - : Arrays.toString(targetNamespaces)); + log.info( + "Registered Controller: '{}' for CRD: '{}' for namespaces: {}", + controller.getClass().getSimpleName(), + resClass, + watchAllNamespaces ? "[all namespaces]" : Arrays.toString(targetNamespaces)); + } } private CustomResourceEventSource createCustomResourceEventSource( diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AbstractConfigurationService.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AbstractConfigurationService.java index 8d0ccb34e4..6d4e36f067 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AbstractConfigurationService.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AbstractConfigurationService.java @@ -10,6 +10,11 @@ public abstract class AbstractConfigurationService implements ConfigurationService { private final Map<String, ControllerConfiguration> configurations = new ConcurrentHashMap<>(); + private final Version version; + + public AbstractConfigurationService(Version version) { + this.version = version; + } protected <R extends CustomResource> void register(ControllerConfiguration<R> config) { final var name = config.getName(); @@ -41,4 +46,9 @@ public <R extends CustomResource> ControllerConfiguration<R> getConfigurationFor public Set<String> getKnownControllerNames() { return configurations.keySet(); } + + @Override + public Version getVersion() { + return version; + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java index 952b1c1838..820d1abb28 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java @@ -5,14 +5,41 @@ import io.javaoperatorsdk.operator.api.ResourceController; import java.util.Set; +/** An interface from which to retrieve configuration information. */ public interface ConfigurationService { + /** + * Retrieves the configuration associated with the specified controller + * + * @param controller the controller we want the configuration of + * @param <R> the {@code CustomResource} type associated with the specified controller + * @return the {@link ControllerConfiguration} associated with the specified controller or {@code + * null} if no configuration exists for the controller + */ <R extends CustomResource> ControllerConfiguration<R> getConfigurationFor( ResourceController<R> controller); + /** + * Retrieves the Kubernetes client configuration + * + * @return the configuration of the Kubernetes client, defaulting to the provided + * auto-configuration + */ default Config getClientConfiguration() { return Config.autoConfigure(null); } + /** + * Retrieves the set of the names of controllers for which a configuration exists + * + * @return the set of known controller names + */ Set<String> getKnownControllerNames(); + + /** + * Retrieves the {@link Version} information associated with this particular instance of the SDK + * + * @return the version information + */ + Version getVersion(); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/Utils.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/Utils.java new file mode 100644 index 0000000000..442b7d47fa --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/Utils.java @@ -0,0 +1,51 @@ +package io.javaoperatorsdk.operator.api.config; + +import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.util.Date; +import java.util.Properties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Utils { + + private static final Logger log = LoggerFactory.getLogger(Utils.class); + + /** + * Attempts to load version information from a properties file produced at build time, currently + * via the {@code git-commit-id-plugin} maven plugin. + * + * @return a {@link Version} object encapsulating the version information + */ + public static Version loadFromProperties() { + final var is = + Thread.currentThread().getContextClassLoader().getResourceAsStream("version.properties"); + + final var properties = new Properties(); + if (is != null) { + try { + properties.load(is); + } catch (IOException e) { + log.warn("Couldn't load version information: {}", e.getMessage()); + } + } else { + log.warn("Couldn't find version.properties file. Default version information will be used."); + } + + Date builtTime; + try { + builtTime = + // RFC 822 date is the default format used by git-commit-id-plugin + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") + .parse(properties.getProperty("git.build.time")); + } catch (ParseException e) { + builtTime = Date.from(Instant.EPOCH); + } + return new Version( + properties.getProperty("git.build.version", "unknown"), + properties.getProperty("git.commit.id.abbrev", "unknown"), + builtTime); + } +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/Version.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/Version.java new file mode 100644 index 0000000000..6af60412b4 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/Version.java @@ -0,0 +1,45 @@ +package io.javaoperatorsdk.operator.api.config; + +import java.util.Date; + +/** A class encapsulating the version information associated with this SDK instance. */ +public class Version { + + private final String sdk; + private final String commit; + private final Date builtTime; + + public Version(String sdkVersion, String commit, Date builtTime) { + this.sdk = sdkVersion; + this.commit = commit; + this.builtTime = builtTime; + } + + /** + * Returns the SDK project version + * + * @return the SDK project version + */ + public String getSdkVersion() { + return sdk; + } + + /** + * Returns the git commit id associated with this SDK instance + * + * @return the git commit id + */ + public String getCommit() { + return commit; + } + + /** + * Returns the date at which this SDK instance was built + * + * @return the build time at which this SDK instance was built or the date corresponding to {@link + * java.time.Instant#EPOCH} if the built time couldn't be retrieved + */ + public Date getBuiltTime() { + return builtTime; + } +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/DefaultEventHandler.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/DefaultEventHandler.java index 5b9fa023e6..92fa88a375 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/DefaultEventHandler.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/DefaultEventHandler.java @@ -17,7 +17,6 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.ThreadFactory; import java.util.concurrent.locks.ReentrantLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,13 +51,7 @@ public DefaultEventHandler( eventBuffer = new EventBuffer(); executor = new ScheduledThreadPoolExecutor( - 5, - new ThreadFactory() { - @Override - public Thread newThread(Runnable runnable) { - return new Thread(runnable, "EventHandler-" + relatedControllerName); - } - }); + 5, runnable -> new Thread(runnable, "EventHandler-" + relatedControllerName)); } public void setEventSourceManager(DefaultEventSourceManager eventSourceManager) { diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/EventDispatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/EventDispatcher.java index 9e951e26c2..d79f8677f5 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/EventDispatcher.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/EventDispatcher.java @@ -3,12 +3,11 @@ import static io.javaoperatorsdk.operator.EventListUtils.containsCustomResourceDeletedEvent; import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getUID; import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getVersion; -import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.markedForDeletion; +import io.fabric8.kubernetes.api.model.KubernetesResourceList; import io.fabric8.kubernetes.client.CustomResource; import io.fabric8.kubernetes.client.dsl.MixedOperation; import io.fabric8.kubernetes.client.dsl.Resource; -import io.javaoperatorsdk.operator.ControllerUtils; import io.javaoperatorsdk.operator.api.Context; import io.javaoperatorsdk.operator.api.DefaultContext; import io.javaoperatorsdk.operator.api.DeleteControl; @@ -16,7 +15,6 @@ import io.javaoperatorsdk.operator.api.UpdateControl; import io.javaoperatorsdk.operator.processing.event.EventList; import io.javaoperatorsdk.operator.processing.event.EventSourceManager; -import java.util.ArrayList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -64,8 +62,7 @@ private PostExecutionControl handleDispatch(ExecutionScope executionScope) { getVersion(resource)); return PostExecutionControl.defaultDispatch(); } - if ((markedForDeletion(resource) - && !ControllerUtils.hasGivenFinalizer(resource, resourceFinalizer))) { + if ((resource.isMarkedForDeletion() && !resource.hasFinalizer(resourceFinalizer))) { log.debug( "Skipping event dispatching since its marked for deletion but has no finalizer: {}", executionScope); @@ -76,7 +73,7 @@ private PostExecutionControl handleDispatch(ExecutionScope executionScope) { eventSourceManager, new EventList(executionScope.getEvents()), executionScope.getRetryInfo()); - if (markedForDeletion(resource)) { + if (resource.isMarkedForDeletion()) { return handleDelete(resource, context); } else { return handleCreateOrUpdate(executionScope, resource, context); @@ -85,8 +82,7 @@ private PostExecutionControl handleDispatch(ExecutionScope executionScope) { private PostExecutionControl handleCreateOrUpdate( ExecutionScope executionScope, CustomResource resource, Context context) { - if (!ControllerUtils.hasGivenFinalizer(resource, resourceFinalizer) - && !markedForDeletion(resource)) { + if (!resource.hasFinalizer(resourceFinalizer) && !resource.isMarkedForDeletion()) { /* We always add the finalizer if missing and not marked for deletion. We execute the controller processing only for processing the event sent as a results of the finalizer add. This will make sure that the resources are not created before @@ -132,7 +128,7 @@ private PostExecutionControl handleDelete(CustomResource resource, Context conte getUID(resource), getVersion(resource)); DeleteControl deleteControl = controller.deleteResource(resource, context); - boolean hasFinalizer = ControllerUtils.hasGivenFinalizer(resource, resourceFinalizer); + boolean hasFinalizer = resource.hasFinalizer(resourceFinalizer); if (deleteControl == DeleteControl.DEFAULT_DELETE && hasFinalizer) { CustomResource customResource = removeFinalizer(resource); return PostExecutionControl.customResourceUpdated(customResource); @@ -150,7 +146,7 @@ private PostExecutionControl handleDelete(CustomResource resource, Context conte private void updateCustomResourceWithFinalizer(CustomResource resource) { log.debug( "Adding finalizer for resource: {} version: {}", getUID(resource), getVersion(resource)); - addFinalizerIfNotPresent(resource); + resource.addFinalizer(resourceFinalizer); replace(resource); } @@ -165,7 +161,7 @@ private CustomResource removeFinalizer(CustomResource resource) { "Removing finalizer on resource: {} with version: {}", getUID(resource), getVersion(resource)); - resource.getMetadata().getFinalizers().remove(resourceFinalizer); + resource.removeFinalizer(resourceFinalizer); return customResourceFacade.replaceWithLock(resource); } @@ -177,27 +173,17 @@ private CustomResource replace(CustomResource resource) { return customResourceFacade.replaceWithLock(resource); } - private void addFinalizerIfNotPresent(CustomResource resource) { - if (!ControllerUtils.hasGivenFinalizer(resource, resourceFinalizer) - && !markedForDeletion(resource)) { - log.info("Adding finalizer to {}", resource.getMetadata()); - if (resource.getMetadata().getFinalizers() == null) { - resource.getMetadata().setFinalizers(new ArrayList<>(1)); - } - resource.getMetadata().getFinalizers().add(resourceFinalizer); - } - } - // created to support unit testing - public static class CustomResourceFacade { + public static class CustomResourceFacade<R extends CustomResource> { - private final MixedOperation<?, ?, Resource<CustomResource>> resourceOperation; + private final MixedOperation<R, KubernetesResourceList<R>, Resource<R>> resourceOperation; - public CustomResourceFacade(MixedOperation<?, ?, Resource<CustomResource>> resourceOperation) { + public CustomResourceFacade( + MixedOperation<R, KubernetesResourceList<R>, Resource<R>> resourceOperation) { this.resourceOperation = resourceOperation; } - public CustomResource updateStatus(CustomResource resource) { + public R updateStatus(R resource) { log.trace("Updating status for resource: {}", resource); return resourceOperation .inNamespace(resource.getMetadata().getNamespace()) @@ -205,7 +191,7 @@ public CustomResource updateStatus(CustomResource resource) { .updateStatus(resource); } - public CustomResource replaceWithLock(CustomResource resource) { + public R replaceWithLock(R resource) { return resourceOperation .inNamespace(resource.getMetadata().getNamespace()) .withName(resource.getMetadata().getName()) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/KubernetesResourceUtils.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/KubernetesResourceUtils.java index 2fd434ce5f..7f617e9705 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/KubernetesResourceUtils.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/KubernetesResourceUtils.java @@ -1,7 +1,6 @@ package io.javaoperatorsdk.operator.processing; import io.fabric8.kubernetes.api.model.HasMetadata; -import io.fabric8.kubernetes.client.CustomResource; public class KubernetesResourceUtils { @@ -12,9 +11,4 @@ public static String getUID(HasMetadata customResource) { public static String getVersion(HasMetadata customResource) { return customResource.getMetadata().getResourceVersion(); } - - public static boolean markedForDeletion(CustomResource resource) { - return resource.getMetadata().getDeletionTimestamp() != null - && !resource.getMetadata().getDeletionTimestamp().isEmpty(); - } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/internal/CustomResourceEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/internal/CustomResourceEventSource.java index c8d13a4d4b..55ed716c45 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/internal/CustomResourceEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/internal/CustomResourceEventSource.java @@ -2,14 +2,12 @@ import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getUID; import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getVersion; -import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.markedForDeletion; import io.fabric8.kubernetes.client.CustomResource; import io.fabric8.kubernetes.client.Watcher; import io.fabric8.kubernetes.client.WatcherException; import io.fabric8.kubernetes.client.dsl.MixedOperation; import io.fabric8.kubernetes.client.dsl.internal.CustomResourceOperationsImpl; -import io.javaoperatorsdk.operator.ControllerUtils; import io.javaoperatorsdk.operator.processing.CustomResourceCache; import io.javaoperatorsdk.operator.processing.KubernetesResourceUtils; import io.javaoperatorsdk.operator.processing.event.AbstractEventSource; @@ -115,7 +113,7 @@ public void eventReceived(Watcher.Action action, CustomResource customResource) } private void markLastGenerationProcessed(CustomResource resource) { - if (generationAware && ControllerUtils.hasGivenFinalizer(resource, resourceFinalizer)) { + if (generationAware && resource.hasFinalizer(resourceFinalizer)) { lastGenerationProcessedSuccessfully.put( KubernetesResourceUtils.getUID(resource), resource.getMetadata().getGeneration()); } @@ -126,7 +124,7 @@ private boolean skipBecauseOfGenerations(CustomResource customResource) { return false; } // if CR being deleted generation is naturally not changing, so we process all the events - if (markedForDeletion(customResource)) { + if (customResource.isMarkedForDeletion()) { return false; } if (!largerGenerationThenProcessedBefore(customResource)) { diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/EventDispatcherTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/EventDispatcherTest.java index 2a8bd942ea..643bb5bab3 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/EventDispatcherTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/EventDispatcherTest.java @@ -33,7 +33,7 @@ class EventDispatcherTest { - private static final String DEFAULT_FINALIZER = "finalizer"; + private static final String DEFAULT_FINALIZER = "javaoperatorsdk.io/finalizer"; private CustomResource testCustomResource; private EventDispatcher eventDispatcher; private ResourceController<CustomResource> controller = mock(ResourceController.class); diff --git a/operator-framework-quarkus-extension/deployment/src/main/java/io/javaoperatorsdk/quarkus/extension/deployment/QuarkusExtensionProcessor.java b/operator-framework-quarkus-extension/deployment/src/main/java/io/javaoperatorsdk/quarkus/extension/deployment/QuarkusExtensionProcessor.java index 446519417b..126ef44446 100644 --- a/operator-framework-quarkus-extension/deployment/src/main/java/io/javaoperatorsdk/quarkus/extension/deployment/QuarkusExtensionProcessor.java +++ b/operator-framework-quarkus-extension/deployment/src/main/java/io/javaoperatorsdk/quarkus/extension/deployment/QuarkusExtensionProcessor.java @@ -7,12 +7,14 @@ import io.javaoperatorsdk.operator.api.config.ConfigurationService; import io.javaoperatorsdk.operator.api.config.ControllerConfiguration; import io.javaoperatorsdk.operator.api.config.RetryConfiguration; +import io.javaoperatorsdk.operator.api.config.Utils; import io.javaoperatorsdk.quarkus.extension.ConfigurationServiceRecorder; import io.javaoperatorsdk.quarkus.extension.ExternalConfiguration; import io.javaoperatorsdk.quarkus.extension.ExternalControllerConfiguration; import io.javaoperatorsdk.quarkus.extension.OperatorProducer; import io.javaoperatorsdk.quarkus.extension.QuarkusConfigurationService; import io.javaoperatorsdk.quarkus.extension.QuarkusControllerConfiguration; +import io.javaoperatorsdk.quarkus.extension.Version; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.SyntheticBeanBuildItem; import io.quarkus.deployment.annotations.BuildProducer; @@ -79,7 +81,12 @@ void createConfigurationServiceAndOperator( .map(ci -> createControllerConfiguration(ci, additionalBeans, reflectionClasses)) .collect(Collectors.toList()); - final var supplier = recorder.configurationServiceSupplier(controllerConfigs); + final var version = Utils.loadFromProperties(); + + final var supplier = + recorder.configurationServiceSupplier( + new Version(version.getSdkVersion(), version.getCommit(), version.getBuiltTime()), + controllerConfigs); syntheticBeanBuildItemBuildProducer.produce( SyntheticBeanBuildItem.configure(QuarkusConfigurationService.class) .scope(Singleton.class) diff --git a/operator-framework-quarkus-extension/runtime/src/main/java/io/javaoperatorsdk/quarkus/extension/ConfigurationServiceRecorder.java b/operator-framework-quarkus-extension/runtime/src/main/java/io/javaoperatorsdk/quarkus/extension/ConfigurationServiceRecorder.java index 8627448bab..d0d6d3dca9 100644 --- a/operator-framework-quarkus-extension/runtime/src/main/java/io/javaoperatorsdk/quarkus/extension/ConfigurationServiceRecorder.java +++ b/operator-framework-quarkus-extension/runtime/src/main/java/io/javaoperatorsdk/quarkus/extension/ConfigurationServiceRecorder.java @@ -3,6 +3,7 @@ import io.fabric8.kubernetes.client.KubernetesClient; import io.javaoperatorsdk.operator.api.config.ConfigurationService; import io.javaoperatorsdk.operator.api.config.ControllerConfiguration; +import io.javaoperatorsdk.operator.api.config.Version; import io.quarkus.arc.Arc; import io.quarkus.runtime.annotations.Recorder; import java.util.List; @@ -12,9 +13,9 @@ public class ConfigurationServiceRecorder { public Supplier<ConfigurationService> configurationServiceSupplier( - List<ControllerConfiguration> controllerConfigs) { + Version version, List<ControllerConfiguration> controllerConfigs) { return () -> new QuarkusConfigurationService( - controllerConfigs, Arc.container().instance(KubernetesClient.class).get()); + version, controllerConfigs, Arc.container().instance(KubernetesClient.class).get()); } } diff --git a/operator-framework-quarkus-extension/runtime/src/main/java/io/javaoperatorsdk/quarkus/extension/QuarkusConfigurationService.java b/operator-framework-quarkus-extension/runtime/src/main/java/io/javaoperatorsdk/quarkus/extension/QuarkusConfigurationService.java index 07cfc30fa1..d18a271cec 100644 --- a/operator-framework-quarkus-extension/runtime/src/main/java/io/javaoperatorsdk/quarkus/extension/QuarkusConfigurationService.java +++ b/operator-framework-quarkus-extension/runtime/src/main/java/io/javaoperatorsdk/quarkus/extension/QuarkusConfigurationService.java @@ -6,6 +6,7 @@ import io.javaoperatorsdk.operator.api.ResourceController; import io.javaoperatorsdk.operator.api.config.AbstractConfigurationService; import io.javaoperatorsdk.operator.api.config.ControllerConfiguration; +import io.javaoperatorsdk.operator.api.config.Version; import io.quarkus.arc.runtime.ClientProxyUnwrapper; import java.util.List; @@ -14,7 +15,8 @@ public class QuarkusConfigurationService extends AbstractConfigurationService { private final KubernetesClient client; public QuarkusConfigurationService( - List<ControllerConfiguration> configurations, KubernetesClient client) { + Version version, List<ControllerConfiguration> configurations, KubernetesClient client) { + super(version); this.client = client; if (configurations != null && !configurations.isEmpty()) { configurations.forEach(this::register); diff --git a/operator-framework-quarkus-extension/runtime/src/main/java/io/javaoperatorsdk/quarkus/extension/Version.java b/operator-framework-quarkus-extension/runtime/src/main/java/io/javaoperatorsdk/quarkus/extension/Version.java new file mode 100644 index 0000000000..354d7628cf --- /dev/null +++ b/operator-framework-quarkus-extension/runtime/src/main/java/io/javaoperatorsdk/quarkus/extension/Version.java @@ -0,0 +1,13 @@ +package io.javaoperatorsdk.quarkus.extension; + +import io.quarkus.runtime.annotations.RecordableConstructor; +import java.util.Date; + +/** Re-publish with a recordable constructor so that quarkus can do its thing with it! */ +public class Version extends io.javaoperatorsdk.operator.api.config.Version { + + @RecordableConstructor + public Version(String sdkVersion, String commit, Date builtTime) { + super(sdkVersion, commit, builtTime); + } +} diff --git a/operator-framework-spring-boot-starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/OperatorAutoConfiguration.java b/operator-framework-spring-boot-starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/OperatorAutoConfiguration.java index 4a8455b69a..ad6f118aa5 100644 --- a/operator-framework-spring-boot-starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/OperatorAutoConfiguration.java +++ b/operator-framework-spring-boot-starter/src/main/java/io/javaoperatorsdk/operator/springboot/starter/OperatorAutoConfiguration.java @@ -11,6 +11,7 @@ import io.javaoperatorsdk.operator.api.ResourceController; import io.javaoperatorsdk.operator.api.config.AbstractConfigurationService; import io.javaoperatorsdk.operator.api.config.RetryConfiguration; +import io.javaoperatorsdk.operator.api.config.Utils; import io.javaoperatorsdk.operator.config.runtime.AnnotationConfiguration; import java.util.List; import java.util.Optional; @@ -26,6 +27,10 @@ public class OperatorAutoConfiguration extends AbstractConfigurationService { @Autowired private OperatorConfigurationProperties configuration; + public OperatorAutoConfiguration() { + super(Utils.loadFromProperties()); + } + @Bean @ConditionalOnMissingBean public KubernetesClient kubernetesClient() { diff --git a/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/AnnotationConfiguration.java b/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/AnnotationConfiguration.java index cce8ac1622..bc2c13ace8 100644 --- a/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/AnnotationConfiguration.java +++ b/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/AnnotationConfiguration.java @@ -5,17 +5,19 @@ import io.javaoperatorsdk.operator.api.Controller; import io.javaoperatorsdk.operator.api.ResourceController; import io.javaoperatorsdk.operator.api.config.ControllerConfiguration; +import java.util.Optional; import java.util.Set; +import java.util.function.Predicate; public class AnnotationConfiguration<R extends CustomResource> implements ControllerConfiguration<R> { private final ResourceController<R> controller; - private final Controller annotation; + private final Optional<Controller> annotation; public AnnotationConfiguration(ResourceController<R> controller) { this.controller = controller; - this.annotation = controller.getClass().getAnnotation(Controller.class); + this.annotation = Optional.ofNullable(controller.getClass().getAnnotation(Controller.class)); } @Override @@ -30,16 +32,15 @@ public String getCRDName() { @Override public String getFinalizer() { - final String annotationFinalizerName = annotation.finalizerName(); - if (!Controller.NULL.equals(annotationFinalizerName)) { - return annotationFinalizerName; - } - return ControllerUtils.getDefaultFinalizerName(getCRDName()); + return annotation + .map(Controller::finalizerName) + .filter(Predicate.not(String::isBlank)) + .orElse(ControllerUtils.getDefaultFinalizerName(getCRDName())); } @Override public boolean isGenerationAware() { - return annotation.generationAwareEventProcessing(); + return annotation.map(Controller::generationAwareEventProcessing).orElse(true); } @Override @@ -49,7 +50,7 @@ public Class<R> getCustomResourceClass() { @Override public Set<String> getNamespaces() { - return Set.of(annotation.namespaces()); + return Set.of(annotation.map(Controller::namespaces).orElse(new String[] {})); } @Override diff --git a/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/DefaultConfigurationService.java b/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/DefaultConfigurationService.java index cadc69b701..73a232c45d 100644 --- a/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/DefaultConfigurationService.java +++ b/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/DefaultConfigurationService.java @@ -5,11 +5,16 @@ import io.javaoperatorsdk.operator.api.config.AbstractConfigurationService; import io.javaoperatorsdk.operator.api.config.ConfigurationService; import io.javaoperatorsdk.operator.api.config.ControllerConfiguration; +import io.javaoperatorsdk.operator.api.config.Utils; public class DefaultConfigurationService extends AbstractConfigurationService { private static final ConfigurationService instance = new DefaultConfigurationService(); + private DefaultConfigurationService() { + super(Utils.loadFromProperties()); + } + public static ConfigurationService instance() { return instance; } diff --git a/samples/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/SchemaController.java b/samples/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/SchemaController.java index 26688ff520..4367333ed4 100644 --- a/samples/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/SchemaController.java +++ b/samples/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/SchemaController.java @@ -6,6 +6,7 @@ import io.fabric8.kubernetes.api.model.SecretBuilder; import io.fabric8.kubernetes.client.KubernetesClient; import io.javaoperatorsdk.operator.api.Context; +import io.javaoperatorsdk.operator.api.Controller; import io.javaoperatorsdk.operator.api.DeleteControl; import io.javaoperatorsdk.operator.api.ResourceController; import io.javaoperatorsdk.operator.api.UpdateControl; @@ -20,6 +21,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +@Controller public class SchemaController implements ResourceController<Schema> { static final String USERNAME_FORMAT = "%s-user"; static final String SECRET_FORMAT = "%s-secret"; diff --git a/samples/quarkus/src/main/java/io/javaoperatorsdk/operator/sample/QuarkusOperator.java b/samples/quarkus/src/main/java/io/javaoperatorsdk/operator/sample/QuarkusOperator.java index 1c0f01953b..9d56fd27ef 100644 --- a/samples/quarkus/src/main/java/io/javaoperatorsdk/operator/sample/QuarkusOperator.java +++ b/samples/quarkus/src/main/java/io/javaoperatorsdk/operator/sample/QuarkusOperator.java @@ -1,8 +1,6 @@ package io.javaoperatorsdk.operator.sample; -import io.fabric8.kubernetes.client.KubernetesClient; import io.javaoperatorsdk.operator.Operator; -import io.javaoperatorsdk.operator.api.config.ConfigurationService; import io.quarkus.runtime.Quarkus; import io.quarkus.runtime.QuarkusApplication; import io.quarkus.runtime.annotations.QuarkusMain; @@ -11,22 +9,15 @@ @QuarkusMain public class QuarkusOperator implements QuarkusApplication { - @Inject KubernetesClient client; - @Inject Operator operator; - @Inject ConfigurationService configuration; - - @Inject CustomServiceController controller; - public static void main(String... args) { Quarkus.run(QuarkusOperator.class, args); } @Override public int run(String... args) throws Exception { - final var config = configuration.getConfigurationFor(controller); - System.out.println("CR class: " + config.getCustomResourceClass()); + operator.start(); Quarkus.waitForExit(); return 0; } diff --git a/samples/tomcat/src/main/java/io/javaoperatorsdk/operator/sample/TomcatController.java b/samples/tomcat/src/main/java/io/javaoperatorsdk/operator/sample/TomcatController.java index c2326e0b53..7fbf54109d 100644 --- a/samples/tomcat/src/main/java/io/javaoperatorsdk/operator/sample/TomcatController.java +++ b/samples/tomcat/src/main/java/io/javaoperatorsdk/operator/sample/TomcatController.java @@ -10,6 +10,7 @@ import io.fabric8.kubernetes.client.dsl.ServiceResource; import io.fabric8.kubernetes.client.utils.Serialization; import io.javaoperatorsdk.operator.api.Context; +import io.javaoperatorsdk.operator.api.Controller; import io.javaoperatorsdk.operator.api.DeleteControl; import io.javaoperatorsdk.operator.api.ResourceController; import io.javaoperatorsdk.operator.api.UpdateControl; @@ -23,6 +24,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +@Controller public class TomcatController implements ResourceController<Tomcat> { private final Logger log = LoggerFactory.getLogger(getClass()); diff --git a/samples/tomcat/src/main/java/io/javaoperatorsdk/operator/sample/WebappController.java b/samples/tomcat/src/main/java/io/javaoperatorsdk/operator/sample/WebappController.java index de32c66fca..b2e4330d5b 100644 --- a/samples/tomcat/src/main/java/io/javaoperatorsdk/operator/sample/WebappController.java +++ b/samples/tomcat/src/main/java/io/javaoperatorsdk/operator/sample/WebappController.java @@ -4,6 +4,7 @@ import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.client.KubernetesClient; import io.javaoperatorsdk.operator.api.Context; +import io.javaoperatorsdk.operator.api.Controller; import io.javaoperatorsdk.operator.api.DeleteControl; import io.javaoperatorsdk.operator.api.ResourceController; import io.javaoperatorsdk.operator.api.UpdateControl; @@ -15,6 +16,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +@Controller public class WebappController implements ResourceController<Webapp> { private KubernetesClient kubernetesClient;