diff --git a/caffeine-bounded-cache-support/src/test/java/io/javaoperatorsdk/operator/processing/event/source/cache/sample/AbstractTestReconciler.java b/caffeine-bounded-cache-support/src/test/java/io/javaoperatorsdk/operator/processing/event/source/cache/sample/AbstractTestReconciler.java
index b6e3ba2c8f..7a53db8bd9 100644
--- a/caffeine-bounded-cache-support/src/test/java/io/javaoperatorsdk/operator/processing/event/source/cache/sample/AbstractTestReconciler.java
+++ b/caffeine-bounded-cache-support/src/test/java/io/javaoperatorsdk/operator/processing/event/source/cache/sample/AbstractTestReconciler.java
@@ -28,7 +28,7 @@
import com.github.benmanes.caffeine.cache.Caffeine;
public abstract class AbstractTestReconciler>
- implements Reconciler
, EventSourceInitializer
{
+ implements Reconciler
{
private static final Logger log =
LoggerFactory.getLogger(BoundedCacheClusterScopeTestReconciler.class);
@@ -82,7 +82,7 @@ public Map prepareEventSources(
Mappers.fromOwnerReference(this instanceof BoundedCacheClusterScopeTestReconciler))
.build(), context);
- return EventSourceInitializer.nameEventSources(es);
+ return EventSourceUtils.nameEventSources(es);
}
private void ensureStatus(P resource) {
diff --git a/docs/_data/sidebar.yml b/docs/_data/sidebar.yml
index 8c083a6cc1..01918ca31f 100644
--- a/docs/_data/sidebar.yml
+++ b/docs/_data/sidebar.yml
@@ -32,4 +32,6 @@
- title: Migrating from v4.3 to v4.4
url: /docs/v4-4-migration
- title: Migrating from v4.4 to v4.5
- url: /docs/v4-5-migration
\ No newline at end of file
+ url: /docs/v4-5-migration
+ - title: Migrating from v4.7 to v5.0
+ url: /docs/v5-0-migration
diff --git a/docs/documentation/dependent-resources.md b/docs/documentation/dependent-resources.md
index 61e18fd3f2..db7a00fda8 100644
--- a/docs/documentation/dependent-resources.md
+++ b/docs/documentation/dependent-resources.md
@@ -101,13 +101,13 @@ and labels, which are ignored by default:
```java
public class MyDependentResource extends KubernetesDependentResource
- implements Matcher {
- // your implementation
+ implements Matcher {
+ // your implementation
- public Result match(MyDependent actualResource, MyPrimary primary,
- Context context) {
- return GenericKubernetesResourceMatcher.match(this, actualResource, primary, context, true);
- }
+ public Result match(MyDependent actualResource, MyPrimary primary,
+ Context context) {
+ return GenericKubernetesResourceMatcher.match(this, actualResource, primary, context, true);
+ }
}
```
@@ -141,24 +141,24 @@ Deleted (or set to be garbage collected). The following example shows how to cre
@KubernetesDependent(labelSelector = WebPageManagedDependentsReconciler.SELECTOR)
class DeploymentDependentResource extends CRUDKubernetesDependentResource {
- public DeploymentDependentResource() {
- super(Deployment.class);
- }
-
- @Override
- protected Deployment desired(WebPage webPage, Context context) {
- var deploymentName = deploymentName(webPage);
- Deployment deployment = loadYaml(Deployment.class, getClass(), "deployment.yaml");
- deployment.getMetadata().setName(deploymentName);
- deployment.getMetadata().setNamespace(webPage.getMetadata().getNamespace());
- deployment.getSpec().getSelector().getMatchLabels().put("app", deploymentName);
-
- deployment.getSpec().getTemplate().getMetadata().getLabels()
- .put("app", deploymentName);
- deployment.getSpec().getTemplate().getSpec().getVolumes().get(0)
- .setConfigMap(new ConfigMapVolumeSourceBuilder().withName(configMapName(webPage)).build());
- return deployment;
- }
+ public DeploymentDependentResource() {
+ super(Deployment.class);
+ }
+
+ @Override
+ protected Deployment desired(WebPage webPage, Context context) {
+ var deploymentName = deploymentName(webPage);
+ Deployment deployment = loadYaml(Deployment.class, getClass(), "deployment.yaml");
+ deployment.getMetadata().setName(deploymentName);
+ deployment.getMetadata().setNamespace(webPage.getMetadata().getNamespace());
+ deployment.getSpec().getSelector().getMatchLabels().put("app", deploymentName);
+
+ deployment.getSpec().getTemplate().getMetadata().getLabels()
+ .put("app", deploymentName);
+ deployment.getSpec().getTemplate().getSpec().getVolumes().get(0)
+ .setConfigMap(new ConfigMapVolumeSourceBuilder().withName(configMapName(webPage)).build());
+ return deployment;
+ }
}
```
@@ -194,25 +194,25 @@ instances are managed by JOSDK, an example of which can be seen below:
```java
@ControllerConfiguration(
- labelSelector = SELECTOR,
- dependents = {
- @Dependent(type = ConfigMapDependentResource.class),
- @Dependent(type = DeploymentDependentResource.class),
- @Dependent(type = ServiceDependentResource.class)
- })
+ labelSelector = SELECTOR,
+ dependents = {
+ @Dependent(type = ConfigMapDependentResource.class),
+ @Dependent(type = DeploymentDependentResource.class),
+ @Dependent(type = ServiceDependentResource.class)
+ })
public class WebPageManagedDependentsReconciler
- implements Reconciler, ErrorStatusHandler {
+ implements Reconciler, ErrorStatusHandler {
- // omitted code
+ // omitted code
- @Override
- public UpdateControl reconcile(WebPage webPage, Context context) {
+ @Override
+ public UpdateControl reconcile(WebPage webPage, Context context) {
- final var name = context.getSecondaryResource(ConfigMap.class).orElseThrow()
- .getMetadata().getName();
- webPage.setStatus(createStatus(name));
- return UpdateControl.patchStatus(webPage);
- }
+ final var name = context.getSecondaryResource(ConfigMap.class).orElseThrow()
+ .getMetadata().getName();
+ webPage.setStatus(createStatus(name));
+ return UpdateControl.patchStatus(webPage);
+ }
}
```
@@ -227,104 +227,11 @@ It is also possible to wire dependent resources programmatically. In practice th
developer is responsible for initializing and managing the dependent resources as well as calling
their `reconcile` method. However, this makes it possible for developers to fully customize the
reconciliation process. Standalone dependent resources should be used in cases when the managed use
-case does not fit.
-
-Note that [Workflows](https://javaoperatorsdk.io/docs/workflows) also can be invoked from standalone
-resources.
+case does not fit. You can, of course, also use [Workflows](https://javaoperatorsdk.io/docs/workflows) when managing
+resources programmatically.
-The following sample is similar to the one above, simply performing additional checks, and
-conditionally creating an `Ingress`:
-
-```java
-
-@ControllerConfiguration
-public class WebPageStandaloneDependentsReconciler
- implements Reconciler, ErrorStatusHandler,
- EventSourceInitializer {
-
- private KubernetesDependentResource configMapDR;
- private KubernetesDependentResource deploymentDR;
- private KubernetesDependentResource serviceDR;
- private KubernetesDependentResource ingressDR;
-
- public WebPageStandaloneDependentsReconciler(KubernetesClient kubernetesClient) {
- // 1.
- createDependentResources(kubernetesClient);
- }
-
- @Override
- public List prepareEventSources(EventSourceContext context) {
- // 2.
- return List.of(
- configMapDR.initEventSource(context),
- deploymentDR.initEventSource(context),
- serviceDR.initEventSource(context));
- }
-
- @Override
- public UpdateControl reconcile(WebPage webPage, Context context) {
-
- // 3.
- if (!isValidHtml(webPage.getHtml())) {
- return UpdateControl.patchStatus(setInvalidHtmlErrorMessage(webPage));
- }
-
- // 4.
- configMapDR.reconcile(webPage, context);
- deploymentDR.reconcile(webPage, context);
- serviceDR.reconcile(webPage, context);
-
- // 5.
- if (Boolean.TRUE.equals(webPage.getSpec().getExposed())) {
- ingressDR.reconcile(webPage, context);
- } else {
- ingressDR.delete(webPage, context);
- }
-
- // 6.
- webPage.setStatus(
- createStatus(configMapDR.getResource(webPage).orElseThrow().getMetadata().getName()));
- return UpdateControl.patchStatus(webPage);
- }
-
- private void createDependentResources(KubernetesClient client) {
- this.configMapDR = new ConfigMapDependentResource();
- this.deploymentDR = new DeploymentDependentResource();
- this.serviceDR = new ServiceDependentResource();
- this.ingressDR = new IngressDependentResource();
-
- Arrays.asList(configMapDR, deploymentDR, serviceDR, ingressDR).forEach(dr -> {
- dr.setKubernetesClient(client);
- dr.configureWith(new KubernetesDependentResourceConfig()
- .setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR));
- });
- }
-
- // omitted code
-}
-```
-
-There are multiple things happening here:
-
-1. Dependent resources are explicitly created and can be access later by reference.
-2. Event sources are produced by the dependent resources, but needs to be explicitly registered in
- this case by implementing
- the [`EventSourceInitializer`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceInitializer.java)
- interface.
-3. The input html is validated, and error message is set in case it is invalid.
-4. Reconciliation of dependent resources is called explicitly, but here the workflow
- customization is fully in the hand of the developer.
-5. An `Ingress` is created but only in case `exposed` flag set to true on custom resource. Tries to
- delete it if not.
-6. Status is set in a different way, this is just an alternative way to show, that the actual state
- can be read using the reference. This could be written in a same way as in the managed example.
-
-See the full source code of
-sample [here](https://github.com/operator-framework/java-operator-sdk/blob/main/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStandaloneDependentsReconciler.java)
-.
-
-Note also the Workflows feature makes it possible to also support this conditional creation use
-case in managed dependent resources.
+You can see a commented example of how to do
+so [here](https://github.com/operator-framework/java-operator-sdk/blob/main/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStandaloneDependentsReconciler.java).
## Creating/Updating Kubernetes Resources
@@ -357,17 +264,17 @@ Since SSA is a complex feature, JOSDK implements a feature flag allowing users t
these implementations. See
in [ConfigurationService](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java#L332-L358).
-It is, however, important to note that these implementations are default, generic
-implementations that the framework can provide expected behavior out of the box. In many
-situations, these will work just fine but it is also possible to provide matching algorithms
+It is, however, important to note that these implementations are default, generic
+implementations that the framework can provide expected behavior out of the box. In many
+situations, these will work just fine but it is also possible to provide matching algorithms
optimized for specific use cases. This is easily done by simply overriding
-the `match(...)` [method](https://github.com/java-operator-sdk/java-operator-sdk/blob/e16559fd41bbb8bef6ce9d1f47bffa212a941b09/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java#L156-L156).
+the `match(...)` [method](https://github.com/java-operator-sdk/java-operator-sdk/blob/e16559fd41bbb8bef6ce9d1f47bffa212a941b09/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java#L156-L156).
-It is also possible to bypass the matching logic altogether to simply rely on the server-side
+It is also possible to bypass the matching logic altogether to simply rely on the server-side
apply mechanism if always sending potentially unchanged resources to the cluster is not an issue.
JOSDK's matching mechanism allows to spare some potentially useless calls to the Kubernetes API
-server. To bypass the matching feature completely, simply override the `match` method to always
-return `false`, thus telling JOSDK that the actual state never matches the desired one, making
+server. To bypass the matching feature completely, simply override the `match` method to always
+return `false`, thus telling JOSDK that the actual state never matches the desired one, making
it always update the resources using SSA.
WARNING: Older versions of Kubernetes before 1.25 would create an additional resource version for every SSA update
@@ -394,20 +301,31 @@ tests [here](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/op
When dealing with multiple dependent resources of same type, the dependent resource implementation
needs to know which specific resource should be targeted when reconciling a given dependent
-resource, since there will be multiple instances of that type which could possibly be used, each
-associated with the same primary resource. In order to do this, JOSDK relies on the
-[resource discriminator](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceDiscriminator.java)
-concept. Resource discriminators uniquely identify the target resource of a dependent resource.
-In the managed Kubernetes dependent resources case, the discriminator can be declaratively set
-using the `@KubernetesDependent` annotation:
-
-```java
-
-@KubernetesDependent(resourceDiscriminator = ConfigMap1Discriminator.class)
-public class MultipleManagedDependentResourceConfigMap1 {
-//...
-}
-```
+resource, since there could be multiple instances of that type which could possibly be used, each
+associated with the same primary resource. In this situation, JOSDK automatically selects the appropriate secondary
+resource matching the desired state associated with the primary resource. This makes sense because the desired
+state computation already needs to be able to discriminate among multiple related secondary resources to tell JOSDK how
+they should be reconciled.
+
+There might be casees, though, where it might be problematic to call the `desired` method several times (for example, because it is costly to do so), it is always possible to override this automated discrimination using several means:
+
+- Implement your own `getSecondaryResource` method on your `DependentResource` implementation from scratch.
+- Override the `selectManagedSecondaryResource` method, if your `DependentResource` extends `AbstractDependentResource`.
+ This should be relatively simple to override this method to optimize the matching to your needs. You can see an
+ example of such an implementation in
+ the [`ExternalWithStateDependentResource`](https://github.com/operator-framework/java-operator-sdk/blob/6cd0f884a7c9b60c81bd2d52da54adbd64d6e118/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/externalstate/ExternalWithStateDependentResource.java#L43-L49)
+ class.
+- Override the `managedSecondaryResourceID` method, if your `DependentResource` extends `KubernetesDependentResource`,
+ where it's very often possible to easily determine the `ResourceID` of the secondary resource. This would probably be
+ the easiest solution if you're working with Kubernetes resources.
+- Configure
+ a [`ResourceDiscriminator`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceDiscriminator.java)
+ implementation for your `DependentResource`. This was the approach that was used before JOSDK v5 but should not be
+ needed anymore as it is simpler and more efficient to override one the methods above instead of creating a separate
+ class. Discriminators can be declaratively set when using managed Kubernetes dependent resources via
+ the `resourceDiscriminator` field of the `@KubernetesDependent` annotation.
+
+### Sharing an Event Source Between Dependent Resources
Dependent resources usually also provide event sources. When dealing with multiple dependents of
the same type, one needs to decide whether these dependent resources should track the same
@@ -423,10 +341,10 @@ would look as follows:
useEventSourceWithName = "configMapSource")
```
-A sample is provided as an integration test both
-for [managed](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleManagedDependentSameTypeIT.java)
-and
-for [standalone](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleDependentResourceIT.java)
+A sample is provided as an integration test both:
+for [managed](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleManagedDependentNoDiscriminatorIT.java)
+
+For [standalone](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleDependentResourceIT.java)
cases.
## Bulk Dependent Resources
@@ -489,15 +407,18 @@ also be created, one per dependent resource.
See [integration test](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/ExternalStateBulkIT.java)
as a sample.
-
## GenericKubernetesResource based Dependent Resources
-In rare circumstances resource handling where there is no class representation or just typeless handling might be needed.
-Fabric8 Client provides [GenericKubernetesResource](https://github.com/fabric8io/kubernetes-client/blob/main/doc/CHEATSHEET.md#resource-typeless-api)
-to support that.
+In rare circumstances resource handling where there is no class representation or just typeless handling might be
+needed.
+Fabric8 Client
+provides [GenericKubernetesResource](https://github.com/fabric8io/kubernetes-client/blob/main/doc/CHEATSHEET.md#resource-typeless-api)
+to support that.
-For dependent resource this is supported by [GenericKubernetesDependentResource](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesDependentResource.java#L8-L8)
-. See samples [here](https://github.com/java-operator-sdk/java-operator-sdk/tree/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/generickubernetesresource).
+For dependent resource this is supported
+by [GenericKubernetesDependentResource](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesDependentResource.java#L8-L8)
+. See
+samples [here](https://github.com/java-operator-sdk/java-operator-sdk/tree/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/generickubernetesresource).
## Other Dependent Resource Features
diff --git a/docs/documentation/features.md b/docs/documentation/features.md
index 564b5233f5..3a3eb0c876 100644
--- a/docs/documentation/features.md
+++ b/docs/documentation/features.md
@@ -66,7 +66,8 @@ and/or re-schedule a reconciliation with a desired time delay:
@Override
public UpdateControl reconcile(
EventSourceTestCustomResource resource, Context context) {
- ...
+ // omitted code
+
return UpdateControl.patchStatus(resource).rescheduleAfter(10, TimeUnit.SECONDS);
}
```
@@ -77,7 +78,8 @@ without an update:
@Override
public UpdateControl reconcile(
EventSourceTestCustomResource resource, Context context) {
- ...
+ // omitted code
+
return UpdateControl.noUpdate().rescheduleAfter(10, TimeUnit.SECONDS);
}
```
@@ -108,7 +110,8 @@ resource are cleaned up in `cleanup` implementation.
```java
public DeleteControl cleanup(MyCustomResource customResource,Context context){
- ...
+ // omitted code
+
return DeleteControl.defaultDelete();
}
@@ -192,10 +195,7 @@ the [WebPage example](https://github.com/java-operator-sdk/java-operator-sdk/blo
```java
public class WebPageStatus extends ObservedGenerationAwareStatus {
-
- private String htmlConfigMap;
-
- ...
+ // omitted code
}
```
@@ -247,11 +247,12 @@ for reconciling deployments.
public class DeploymentReconciler
implements Reconciler, TestExecutionInfoProvider {
- @Override
- public UpdateControl reconcile(
- Deployment resource, Context context) {
- ...
- }
+ @Override
+ public UpdateControl reconcile(
+ Deployment resource, Context context) {
+ // omitted code
+ }
+}
```
## Max Interval Between Reconciliations
@@ -269,6 +270,7 @@ standard annotation:
@ControllerConfiguration(maxReconciliationInterval = @MaxReconciliationInterval(
interval = 50,
timeUnit = TimeUnit.MILLISECONDS))
+public class MyReconciler implements Reconciler {}
```
The event is not propagated at a fixed rate, rather it's scheduled after each reconciliation. So the
@@ -491,9 +493,8 @@ related [method](https://github.com/java-operator-sdk/java-operator-sdk/blob/mai
### Registering Event Sources
-To register event sources, your `Reconciler` has to implement the
-[`EventSourceInitializer`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceInitializer.java)
-interface and initialize a list of event sources to register. One way to see this in action is
+To register event sources, your `Reconciler` has to override the `prepareEventSources` and return
+list of event sources to register. One way to see this in action is
to look at the
[tomcat example](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatReconciler.java)
(irrelevant details omitted):
@@ -501,8 +502,10 @@ to look at the
```java
@ControllerConfiguration
-public class TomcatReconciler implements Reconciler, EventSourceInitializer {
+public class TomcatReconciler implements Reconciler {
+ // omitted code
+
@Override
public List prepareEventSources(EventSourceContext context) {
var configMapEventSource =
@@ -511,9 +514,9 @@ public class TomcatReconciler implements Reconciler, EventSourceInitiali
.withSecondaryToPrimaryMapper(
Mappers.fromAnnotation(ANNOTATION_NAME, ANNOTATION_NAMESPACE)
.build(), context));
- return EventSourceInitializer.nameEventSources(configMapEventSource);
+ return EventSourceUtils.nameEventSources(configMapEventSource);
}
- ...
+
}
```
@@ -657,15 +660,15 @@ registering an associated `Informer` and then calling the `changeNamespaces` met
```java
-public static void main(String[]args)throws IOException{
- KubernetesClient client=new DefaultKubernetesClient();
- Operator operator=new Operator(client);
- RegisteredController registeredController=operator.register(new WebPageReconciler(client));
- operator.installShutdownHook();
- operator.start();
+public static void main(String[] args) {
+ KubernetesClient client = new DefaultKubernetesClient();
+ Operator operator = new Operator(client);
+ RegisteredController registeredController = operator.register(new WebPageReconciler(client));
+ operator.installShutdownHook();
+ operator.start();
- // call registeredController further while operator is running
- }
+ // call registeredController further while operator is running
+}
```
@@ -677,8 +680,7 @@ configured appropriately so that the `followControllerNamespaceChanges` method r
```java
@ControllerConfiguration
-public class MyReconciler
- implements Reconciler, EventSourceInitializer {
+public class MyReconciler implements Reconciler {
@Override
public Map prepareEventSources(
@@ -689,7 +691,7 @@ public class MyReconciler
.withNamespacesInheritedFromController(context)
.build(), context);
- return EventSourceInitializer.nameEventSources(configMapES);
+ return EventSourceUtils.nameEventSources(configMapES);
}
}
@@ -768,8 +770,8 @@ You can use a different implementation by overriding the default one provided by
follows:
```java
-Metrics metrics= …;
-Operator operator = new Operator(client, o -> o.withMetrics());
+Metrics metrics; // initialize your metrics implementation
+Operator operator = new Operator(client, o -> o.withMetrics(metrics));
```
### Micrometer implementation
@@ -785,8 +787,8 @@ To create a `MicrometerMetrics` implementation that behaves how it has historica
instance via:
```java
-MeterRegistry registry= …;
-Metrics metrics=new MicrometerMetrics(registry)
+MeterRegistry registry; // initialize your registry implementation
+Metrics metrics = new MicrometerMetrics(registry);
```
Note, however, that this constructor is deprecated and we encourage you to use the factory methods instead, which either
@@ -802,7 +804,7 @@ basis, deleting the associated meters after 5 seconds when a resource is deleted
MicrometerMetrics.newPerResourceCollectingMicrometerMetricsBuilder(registry)
.withCleanUpDelayInSeconds(5)
.withCleaningThreadNumber(2)
- .build()
+ .build();
```
The micrometer implementation records the following metrics:
diff --git a/docs/documentation/v5-0-migration.md b/docs/documentation/v5-0-migration.md
new file mode 100644
index 0000000000..b34a7a6b21
--- /dev/null
+++ b/docs/documentation/v5-0-migration.md
@@ -0,0 +1,28 @@
+---
+title: Migrating from v4.7 to v5.0
+description: Migrating from v4.7 to v5.0
+layout: docs
+permalink: /docs/v5-0-migration
+---
+
+# Migrating from v4.7 to v5.0
+
+## API Tweaks
+
+1. [Result of managed dependent resources](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedDependentResourceContext.java#L55-L57)
+ is not `Optional` anymore. In case you use this result, simply use the result
+ objects directly.
+2. `EventSourceInitializer` is not a separate interface anymore. It is part of the `Reconciler` interface with a
+ default implementation. You can simply remove this interface from your reconciler. The
+ [`EventSourceUtils`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceUtils.java#L11-L11)
+ now contains all the utility methods used for event sources naming that were previously defined in
+ the `EventSourceInitializer` interface.
+3. Patching status through `UpdateControl` like the `patchStatus` method now by default
+ uses Server Side Apply instead of simple patch. To use the former approach, use the feature flag
+ in [`ConfigurationService`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java#L400-L400)
+ !!! IMPORTANT !!!
+ Migration from a non-SSA based controller to SSA based controller can cause problems, due to known issues.
+ See the following [integration test](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/StatusPatchSSAMigrationIT.java#L71-L82) where it is demonstrated.
+ Also, the related part of a [workaround](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/StatusPatchSSAMigrationIT.java#L110-L116).
+4. `ManagedDependentResourceContext` has been renamed to `ManagedWorkflowAndDependentResourceContext` and is accessed
+ via the accordingly renamed `managedWorkflowAndDependentResourceContext` method.
diff --git a/docs/documentation/workflows.md b/docs/documentation/workflows.md
index 7190f8e8e3..ea83016430 100644
--- a/docs/documentation/workflows.md
+++ b/docs/documentation/workflows.md
@@ -122,7 +122,7 @@ page sample):
@ControllerConfiguration(
labelSelector = WebPageDependentsWorkflowReconciler.DEPENDENT_RESOURCE_LABEL_SELECTOR)
public class WebPageDependentsWorkflowReconciler
- implements Reconciler, ErrorStatusHandler, EventSourceInitializer {
+ implements Reconciler, ErrorStatusHandler {
public static final String DEPENDENT_RESOURCE_LABEL_SELECTOR = "!low-level";
private static final Logger log =
@@ -147,7 +147,7 @@ public class WebPageDependentsWorkflowReconciler
@Override
public Map prepareEventSources(EventSourceContext context) {
- return EventSourceInitializer.nameEventSources(
+ return EventSourceUtils.nameEventSources(
configMapDR.initEventSource(context),
deploymentDR.initEventSource(context),
serviceDR.initEventSource(context),
@@ -338,6 +338,34 @@ and NOT `CRUDKubernetesDependentResource` since otherwise the Kubernetes Garbage
In other words if a Kubernetes Dependent Resource depends on another dependent resource, it should not implement
`GargageCollected` interface, otherwise the deletion order won't be guaranteed.
+
+## Explicit Managed Workflow Invocation
+
+Managed workflows, i.e. ones that are declared via annotations and therefore completely managed by JOSDK, are reconciled
+before the primary resource. Each dependent resource that can be reconciled (according to the workflow configuration)
+will therefore be reconciled before the primary reconciler is called to reconcile the primary resource. There are,
+however, situations where it would be be useful to perform additional steps before the workflow is reconciled, for
+example to validate the current state, execute arbitrary logic or even skip reconciliation altogether. Explicit
+invocation of managed workflow was therefore introduced to solve these issues.
+
+To use this feature, you need to set the `explicitInvocation` field to `true` on the `@Workflow` annotation and then
+call the `reconcileManagedWorkflow` method from the `
+ManagedWorkflowAndDependentResourceContext` retrieved from the reconciliation `Context` provided as part of your primary
+resource reconciler `reconcile` method arguments.
+
+See
+related [integration test](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowExplicitInvocationIT.java)
+for more details.
+
+For `cleanup`, if the `Cleaner` interface is implemented, the `cleanupManageWorkflow()` needs to be called explicitly.
+However, if `Cleaner` interface is not implemented, it will be called implicitly.
+See
+related [integration test](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowExplicitCleanupIT.java).
+
+While nothing prevents calling the workflow multiple times in a reconciler, it isn't typical or even recommended to do
+so. Conversely, if explicit invocation is requested but `reconcileManagedWorkflow` is not called in the primary resource
+reconciler, the workflow won't be reconciled at all.
+
## Notes and Caveats
- Delete is almost always called on every resource during the cleanup. However, it might be the case
diff --git a/micrometer-support/pom.xml b/micrometer-support/pom.xml
index 0ecfd38d9c..6ca02379b1 100644
--- a/micrometer-support/pom.xml
+++ b/micrometer-support/pom.xml
@@ -5,18 +5,13 @@
java-operator-sdk
io.javaoperatorsdk
- 4.8.1-SNAPSHOT
+ 5.0.0-SNAPSHOT
4.0.0
micrometer-support
Operator SDK - Micrometer Support
-
-
- 11
- 11
-
-
+
io.micrometer
diff --git a/operator-framework-bom/pom.xml b/operator-framework-bom/pom.xml
index 4367d104d2..b4d013bc90 100644
--- a/operator-framework-bom/pom.xml
+++ b/operator-framework-bom/pom.xml
@@ -5,7 +5,7 @@
io.javaoperatorsdk
operator-framework-bom
- 4.8.1-SNAPSHOT
+ 5.0.0-SNAPSHOT
Operator SDK - Bill of Materials
pom
Java SDK for implementing Kubernetes operators
diff --git a/operator-framework-core/pom.xml b/operator-framework-core/pom.xml
index 9f29d69e28..61eb1ce733 100644
--- a/operator-framework-core/pom.xml
+++ b/operator-framework-core/pom.xml
@@ -6,7 +6,7 @@
io.javaoperatorsdk
java-operator-sdk
- 4.8.1-SNAPSHOT
+ 5.0.0-SNAPSHOT
../pom.xml
@@ -81,7 +81,7 @@
org.apache.logging.log4j
- log4j-slf4j-impl
+ log4j-slf4j2-impl
test
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java
index d908f52ebe..846c16046c 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java
@@ -19,14 +19,14 @@
import io.javaoperatorsdk.operator.ReconcilerUtils;
import io.javaoperatorsdk.operator.api.config.Utils.Configurator;
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
+import io.javaoperatorsdk.operator.api.config.workflow.WorkflowSpec;
import io.javaoperatorsdk.operator.api.reconciler.Constants;
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.api.reconciler.dependent.DependentResource;
import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition;
import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter;
-import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;
-import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilters;
import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter;
import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter;
import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter;
@@ -99,6 +99,7 @@ public ControllerConfiguration getConfigurationFor(
protected ControllerConfiguration
configFor(Reconciler
reconciler) {
final var annotation = reconciler.getClass().getAnnotation(
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration.class);
+
if (annotation == null) {
throw new OperatorException(
"Missing mandatory @"
@@ -163,49 +164,26 @@ protected
ControllerConfiguration
configFor(Reconcile
Utils.instantiate(annotation.itemStore(), ItemStore.class, context), dependentFieldManager,
this, informerListLimit);
- ResourceEventFilter
answer = deprecatedEventFilter(annotation);
- config.setEventFilter(answer != null ? answer : ResourceEventFilters.passthrough());
- List specs = dependentResources(annotation, config);
- config.setDependentResources(specs);
+ final var workflowAnnotation = reconciler.getClass().getAnnotation(
+ io.javaoperatorsdk.operator.api.reconciler.Workflow.class);
+ if (workflowAnnotation != null) {
+ List specs = dependentResources(workflowAnnotation, config);
+ WorkflowSpec workflowSpec = new WorkflowSpec(specs, workflowAnnotation.explicitInvocation());
+ config.setWorkflowSpec(workflowSpec);
+ }
return config;
}
- @SuppressWarnings("unchecked")
- private static ResourceEventFilter
deprecatedEventFilter(
- io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration annotation) {
- ResourceEventFilter
answer = null;
-
- Class>[] filterTypes =
- (Class>[]) valueOrDefault(annotation,
- io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::eventFilters,
- new Object[] {});
- for (var filterType : filterTypes) {
- try {
- ResourceEventFilter filter = filterType.getConstructor().newInstance();
-
- if (answer == null) {
- answer = filter;
- } else {
- answer = answer.and(filter);
- }
- } catch (Exception e) {
- throw new IllegalArgumentException(e);
- }
- }
- return answer;
- }
-
@SuppressWarnings({"unchecked", "rawtypes"})
private static List dependentResources(
- io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration annotation,
+ Workflow annotation,
ControllerConfiguration> parent) {
- final var dependents =
- valueOrDefault(annotation,
- io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::dependents,
- new Dependent[] {});
- if (dependents.length == 0) {
+ final var dependents = annotation.dependents();
+
+
+ if (dependents == null || dependents.length == 0) {
return Collections.emptyList();
}
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 53bfc75df9..a55502a691 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
@@ -391,4 +391,14 @@ default boolean parseResourceVersionsForEventFilteringAndCaching() {
return false;
}
+ /**
+ * {@link io.javaoperatorsdk.operator.api.reconciler.UpdateControl} patchStatus can either use
+ * simple update or SSA for status subresource patching.
+ *
+ * @return true by default
+ */
+ default boolean useSSAForResourceStatusPatch() {
+ return true;
+ }
+
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationServiceOverrider.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationServiceOverrider.java
index 5879383464..6116af5c0f 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationServiceOverrider.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationServiceOverrider.java
@@ -40,6 +40,7 @@ public class ConfigurationServiceOverrider {
private Set> defaultNonSSAResource;
private Boolean previousAnnotationForDependentResources;
private Boolean parseResourceVersions;
+ private Boolean useSSAForResourceStatusPatch;
private DependentResourceFactory dependentResourceFactory;
ConfigurationServiceOverrider(ConfigurationService original) {
@@ -174,12 +175,17 @@ public ConfigurationServiceOverrider withPreviousAnnotationForDependentResources
return this;
}
- public ConfigurationServiceOverrider wihtParseResourceVersions(
+ public ConfigurationServiceOverrider withParseResourceVersions(
boolean value) {
this.parseResourceVersions = value;
return this;
}
+ public ConfigurationServiceOverrider withUseSSAForResourceStatusPatch(boolean value) {
+ this.useSSAForResourceStatusPatch = value;
+ return this;
+ }
+
public ConfigurationService build() {
return new BaseConfigurationService(original.getVersion(), cloner, client) {
@Override
@@ -312,6 +318,14 @@ public boolean parseResourceVersionsForEventFilteringAndCaching() {
? parseResourceVersions
: super.parseResourceVersionsForEventFilteringAndCaching();
}
+
+ @Override
+ public boolean useSSAForResourceStatusPatch() {
+ return useSSAForResourceStatusPatch != null
+ ? useSSAForResourceStatusPatch
+ : super.useSSAForResourceStatusPatch();
+
+ }
};
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java
index 13ddd995ad..2031283f37 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java
@@ -1,21 +1,16 @@
package io.javaoperatorsdk.operator.api.config;
import java.time.Duration;
-import java.util.Collections;
-import java.util.List;
import java.util.Optional;
import java.util.Set;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.ReconcilerUtils;
-import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
+import io.javaoperatorsdk.operator.api.config.workflow.WorkflowSpec;
import io.javaoperatorsdk.operator.api.reconciler.MaxReconciliationInterval;
import io.javaoperatorsdk.operator.processing.event.rate.LinearRateLimiter;
import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter;
-import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;
-import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilters;
import io.javaoperatorsdk.operator.processing.retry.GenericRetry;
-import io.javaoperatorsdk.operator.processing.retry.GradualRetry;
import io.javaoperatorsdk.operator.processing.retry.Retry;
public interface ControllerConfiguration extends ResourceConfiguration
{
@@ -60,22 +55,7 @@ default boolean isGenerationAware() {
String getAssociatedReconcilerClassName();
default Retry getRetry() {
- final var configuration = getRetryConfiguration();
- return !RetryConfiguration.DEFAULT.equals(configuration)
- ? GenericRetry.fromConfiguration(configuration)
- : GenericRetry.DEFAULT; // NOSONAR
- }
-
- /**
- * Use {@link #getRetry()} instead.
- *
- * @return configuration for retry.
- * @deprecated provide your own {@link Retry} implementation or use the {@link GradualRetry}
- * annotation instead
- */
- @Deprecated(forRemoval = true)
- default RetryConfiguration getRetryConfiguration() {
- return RetryConfiguration.DEFAULT;
+ return GenericRetry.DEFAULT;
}
@SuppressWarnings("rawtypes")
@@ -83,28 +63,8 @@ default RateLimiter getRateLimiter() {
return DEFAULT_RATE_LIMITER;
}
- /**
- * Allow controllers to filter events before they are passed to the
- * {@link io.javaoperatorsdk.operator.processing.event.EventHandler}.
- *
- *
- * Resource event filters only applies on events of the main custom resource. Not on events from
- * other event sources nor the periodic events.
- *
- *
- * @return filter
- * @deprecated use {@link ResourceConfiguration#onAddFilter()},
- * {@link ResourceConfiguration#onUpdateFilter()} or
- * {@link ResourceConfiguration#genericFilter()} instead
- */
- @Deprecated(forRemoval = true)
- default ResourceEventFilter getEventFilter() {
- return ResourceEventFilters.passthrough();
- }
-
- @SuppressWarnings("rawtypes")
- default List getDependentResources() {
- return Collections.emptyList();
+ default Optional getWorkflowSpec() {
+ return Optional.empty();
}
default Optional maxReconciliationInterval() {
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java
index 328d912109..9f98214b91 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java
@@ -10,12 +10,11 @@
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.client.informers.cache.ItemStore;
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
+import io.javaoperatorsdk.operator.api.config.workflow.WorkflowSpec;
import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter;
-import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;
import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter;
import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter;
import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter;
-import io.javaoperatorsdk.operator.processing.retry.GenericRetry;
import io.javaoperatorsdk.operator.processing.retry.Retry;
import static io.javaoperatorsdk.operator.api.reconciler.Constants.DEFAULT_NAMESPACES_SET;
@@ -29,7 +28,6 @@ public class ControllerConfigurationOverrider {
private Set namespaces;
private Retry retry;
private String labelSelector;
- private ResourceEventFilter customResourcePredicate;
private final ControllerConfiguration original;
private Duration reconciliationMaxInterval;
private OnAddFilter super R> onAddFilter;
@@ -41,6 +39,7 @@ public class ControllerConfigurationOverrider {
private String name;
private String fieldManager;
private Long informerListLimit;
+ private WorkflowSpec workflowSpec;
private ControllerConfigurationOverrider(ControllerConfiguration original) {
this.finalizer = original.getFinalizerName();
@@ -48,7 +47,6 @@ private ControllerConfigurationOverrider(ControllerConfiguration original) {
this.namespaces = new HashSet<>(original.getNamespaces());
this.retry = original.getRetry();
this.labelSelector = original.getLabelSelector();
- this.customResourcePredicate = original.getEventFilter();
this.reconciliationMaxInterval = original.maxReconciliationInterval().orElse(null);
this.onAddFilter = original.onAddFilter().orElse(null);
this.onUpdateFilter = original.onUpdateFilter().orElse(null);
@@ -59,6 +57,7 @@ private ControllerConfigurationOverrider(ControllerConfiguration original) {
this.fieldManager = original.fieldManager();
this.informerListLimit = original.getInformerListLimit().orElse(null);
this.itemStore = original.getItemStore().orElse(null);
+ this.workflowSpec = original.getWorkflowSpec().orElse(null);
}
public ControllerConfigurationOverrider withFinalizer(String finalizer) {
@@ -110,17 +109,6 @@ public ControllerConfigurationOverrider watchingAllNamespaces() {
return this;
}
- /**
- * @param retry configuration
- * @return current instance of overrider
- * @deprecated Use {@link #withRetry(Retry)} instead
- */
- @Deprecated(forRemoval = true)
- public ControllerConfigurationOverrider withRetry(RetryConfiguration retry) {
- this.retry = GenericRetry.fromConfiguration(retry);
- return this;
- }
-
public ControllerConfigurationOverrider withRetry(Retry retry) {
this.retry = retry;
return this;
@@ -136,12 +124,6 @@ public ControllerConfigurationOverrider withLabelSelector(String labelSelecto
return this;
}
- public ControllerConfigurationOverrider withCustomResourcePredicate(
- ResourceEventFilter customResourcePredicate) {
- this.customResourcePredicate = customResourcePredicate;
- return this;
- }
-
public ControllerConfigurationOverrider withReconciliationMaxInterval(
Duration reconciliationMaxInterval) {
this.reconciliationMaxInterval = reconciliationMaxInterval;
@@ -196,7 +178,7 @@ public ControllerConfigurationOverrider withInformerListLimit(
public ControllerConfigurationOverrider replacingNamedDependentResourceConfig(String name,
Object dependentResourceConfig) {
- final var specs = original.getDependentResources();
+ final var specs = original.getWorkflowSpec().orElseThrow().getDependentResourceSpecs();
final var spec = specs.stream()
.filter(drs -> drs.getName().equals(name)).findFirst()
.orElseThrow(
@@ -210,15 +192,13 @@ public ControllerConfigurationOverrider replacingNamedDependentResourceConfig
}
public ControllerConfiguration build() {
- final var overridden = new ResolvedControllerConfiguration<>(original.getResourceClass(),
+ return new ResolvedControllerConfiguration<>(original.getResourceClass(),
name,
generationAware, original.getAssociatedReconcilerClassName(), retry, rateLimiter,
reconciliationMaxInterval, onAddFilter, onUpdateFilter, genericFilter,
- original.getDependentResources(),
namespaces, finalizer, labelSelector, configurations, itemStore, fieldManager,
- original.getConfigurationService(), informerListLimit);
- overridden.setEventFilter(customResourcePredicate);
- return overridden;
+ original.getConfigurationService(), informerListLimit,
+ original.getWorkflowSpec().orElse(null));
}
public static ControllerConfigurationOverrider override(
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultRetryConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultRetryConfiguration.java
deleted file mode 100644
index 40fbb38aa7..0000000000
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultRetryConfiguration.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package io.javaoperatorsdk.operator.api.config;
-
-public class DefaultRetryConfiguration implements RetryConfiguration {
-
-}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResolvedControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResolvedControllerConfiguration.java
index 307e75080f..9a94d5d667 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResolvedControllerConfiguration.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResolvedControllerConfiguration.java
@@ -8,9 +8,9 @@
import io.fabric8.kubernetes.client.informers.cache.ItemStore;
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceConfigurationProvider;
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
+import io.javaoperatorsdk.operator.api.config.workflow.WorkflowSpec;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter;
-import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;
import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter;
import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter;
import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter;
@@ -33,9 +33,7 @@ public class ResolvedControllerConfiguration
private final ItemStore
itemStore;
private final ConfigurationService configurationService;
private final String fieldManager;
-
- private ResourceEventFilter
eventFilter;
- private List dependentResources;
+ private WorkflowSpec workflowSpec;
public ResolvedControllerConfiguration(Class resourceClass, ControllerConfiguration
other) {
this(resourceClass, other.getName(), other.isGenerationAware(),
@@ -43,11 +41,11 @@ public ResolvedControllerConfiguration(Class
resourceClass, ControllerConfigu
other.maxReconciliationInterval().orElse(null),
other.onAddFilter().orElse(null), other.onUpdateFilter().orElse(null),
other.genericFilter().orElse(null),
- other.getDependentResources(), other.getNamespaces(),
+ other.getNamespaces(),
other.getFinalizerName(), other.getLabelSelector(), Collections.emptyMap(),
other.getItemStore().orElse(null), other.fieldManager(),
other.getConfigurationService(),
- other.getInformerListLimit().orElse(null));
+ other.getInformerListLimit().orElse(null), other.getWorkflowSpec().orElse(null));
}
public static Duration getMaxReconciliationInterval(long interval, TimeUnit timeUnit) {
@@ -72,16 +70,16 @@ public ResolvedControllerConfiguration(Class
resourceClass, String name,
RateLimiter rateLimiter, Duration maxReconciliationInterval,
OnAddFilter super P> onAddFilter, OnUpdateFilter super P> onUpdateFilter,
GenericFilter super P> genericFilter,
- List dependentResources,
Set namespaces, String finalizer, String labelSelector,
Map configurations, ItemStore itemStore,
String fieldManager,
- ConfigurationService configurationService, Long informerListLimit) {
+ ConfigurationService configurationService, Long informerListLimit,
+ WorkflowSpec workflowSpec) {
this(resourceClass, name, generationAware, associatedReconcilerClassName, retry, rateLimiter,
maxReconciliationInterval, onAddFilter, onUpdateFilter, genericFilter,
namespaces, finalizer, labelSelector, configurations, itemStore, fieldManager,
configurationService, informerListLimit);
- setDependentResources(dependentResources);
+ setWorkflowSpec(workflowSpec);
}
protected ResolvedControllerConfiguration(Class
resourceClass, String name,
@@ -107,6 +105,7 @@ protected ResolvedControllerConfiguration(Class
resourceClass, String name,
this.finalizer =
ControllerConfiguration.ensureValidFinalizerName(finalizer, getResourceTypeName());
this.fieldManager = fieldManager;
+ this.workflowSpec = workflowSpec;
}
protected ResolvedControllerConfiguration(Class
resourceClass, String name,
@@ -146,14 +145,14 @@ public RateLimiter getRateLimiter() {
return rateLimiter;
}
+
@Override
- public List getDependentResources() {
- return dependentResources;
+ public Optional getWorkflowSpec() {
+ return Optional.ofNullable(workflowSpec);
}
- protected void setDependentResources(List dependentResources) {
- this.dependentResources = dependentResources == null ? Collections.emptyList()
- : Collections.unmodifiableList(dependentResources);
+ public void setWorkflowSpec(WorkflowSpec workflowSpec) {
+ this.workflowSpec = workflowSpec;
}
@Override
@@ -166,21 +165,6 @@ public ConfigurationService getConfigurationService() {
return configurationService;
}
- @Override
- public ResourceEventFilter getEventFilter() {
- return eventFilter;
- }
-
- /**
- * @deprecated Use {@link OnAddFilter}, {@link OnUpdateFilter} and {@link GenericFilter} instead
- *
- * @param eventFilter generic event filter
- */
- @Deprecated(forRemoval = true)
- protected void setEventFilter(ResourceEventFilter
eventFilter) {
- this.eventFilter = eventFilter;
- }
-
@Override
public Object getConfigurationFor(DependentResourceSpec spec) {
return configurations.get(spec);
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/RetryConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/RetryConfiguration.java
deleted file mode 100644
index b293c7e33f..0000000000
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/RetryConfiguration.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package io.javaoperatorsdk.operator.api.config;
-
-import io.javaoperatorsdk.operator.processing.retry.GradualRetry;
-
-/**
- * @deprecated specify your own {@link io.javaoperatorsdk.operator.processing.retry.Retry}
- * implementation or use {@link GradualRetry} annotation instead
- */
-@Deprecated(forRemoval = true)
-public interface RetryConfiguration {
-
- RetryConfiguration DEFAULT = new DefaultRetryConfiguration();
-
- int DEFAULT_MAX_ATTEMPTS = 5;
- long DEFAULT_INITIAL_INTERVAL = 2000L;
- double DEFAULT_MULTIPLIER = 1.5D;
-
- default int getMaxAttempts() {
- return DEFAULT_MAX_ATTEMPTS;
- }
-
- default long getInitialInterval() {
- return DEFAULT_INITIAL_INTERVAL;
- }
-
- default double getIntervalMultiplier() {
- return DEFAULT_MULTIPLIER;
- }
-
- default long getMaxInterval() {
- return (long) (DEFAULT_INITIAL_INTERVAL * Math.pow(DEFAULT_MULTIPLIER, DEFAULT_MAX_ATTEMPTS));
- }
-}
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
index 19d8a73689..d318560480 100644
--- 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
@@ -6,7 +6,6 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
-import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.Arrays;
import java.util.Date;
@@ -34,11 +33,8 @@ public class Utils {
* via the {@code git-commit-id-plugin} maven plugin.
*
* @return a {@link Version} object encapsulating the version information
- * @deprecated use {@link #VERSION} instead, as this method will be made internal in a future
- * release
*/
- @Deprecated
- public static Version loadFromProperties() {
+ private static Version loadFromProperties() {
final var is =
Thread.currentThread().getContextClassLoader().getResourceAsStream("version.properties");
@@ -57,9 +53,7 @@ public static Version loadFromProperties() {
try {
String time = properties.getProperty("git.build.time");
if (time != null) {
- builtTime =
- // RFC 822 date is the default format used by git-commit-id-plugin
- new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse(time);
+ builtTime = Date.from(Instant.parse(time));
} else {
builtTime = Date.from(Instant.EPOCH);
}
@@ -82,9 +76,8 @@ public static int ensureValid(int value, String description, int minValue, int d
throw new IllegalArgumentException(
"Default value for " + description + " must be greater than " + minValue);
}
- log.warn("Requested " + description + " should be greater than " + minValue + ". Requested: "
- + value + ", using " + defaultValue + (defaultValue == minValue ? "" : " (default)") +
- " instead");
+ log.warn("Requested {} should be greater than {}. Requested: {}, using {}{} instead",
+ description, minValue, value, defaultValue, defaultValue == minValue ? "" : " (default)");
value = defaultValue;
}
return value;
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/workflow/WorkflowSpec.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/workflow/WorkflowSpec.java
new file mode 100644
index 0000000000..ab89bb07db
--- /dev/null
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/workflow/WorkflowSpec.java
@@ -0,0 +1,26 @@
+package io.javaoperatorsdk.operator.api.config.workflow;
+
+import java.util.List;
+
+import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
+
+public class WorkflowSpec {
+
+ @SuppressWarnings("rawtypes")
+ private final List dependentResourceSpecs;
+ private final boolean explicitInvocation;
+
+ public WorkflowSpec(List dependentResourceSpecs,
+ boolean explicitInvocation) {
+ this.dependentResourceSpecs = dependentResourceSpecs;
+ this.explicitInvocation = explicitInvocation;
+ }
+
+ public List getDependentResourceSpecs() {
+ return dependentResourceSpecs;
+ }
+
+ public boolean isExplicitInvocation() {
+ return explicitInvocation;
+ }
+}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Context.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Context.java
index e157ed5fd7..a997835822 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Context.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Context.java
@@ -8,7 +8,7 @@
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
-import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.ManagedDependentResourceContext;
+import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.ManagedWorkflowAndDependentResourceContext;
import io.javaoperatorsdk.operator.processing.event.EventSourceRetriever;
import io.javaoperatorsdk.operator.processing.event.source.IndexerResourceCache;
@@ -34,7 +34,14 @@ Optional getSecondaryResource(Class expectedType,
ControllerConfiguration getControllerConfiguration();
- ManagedDependentResourceContext managedDependentResourceContext();
+ /**
+ * Retrieve the {@link ManagedWorkflowAndDependentResourceContext} used to interact with
+ * {@link io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource}s and associated
+ * {@link io.javaoperatorsdk.operator.processing.dependent.workflow.Workflow}
+ *
+ * @return the {@link ManagedWorkflowAndDependentResourceContext}
+ */
+ ManagedWorkflowAndDependentResourceContext managedWorkflowAndDependentResourceContext();
EventSourceRetriever
eventSourceRetriever();
@@ -51,5 +58,18 @@ Optional getSecondaryResource(Class expectedType,
* @return the {@link IndexerResourceCache} associated with the associated {@link Reconciler} for
* this context
*/
+ @SuppressWarnings("unused")
IndexedResourceCache getPrimaryCache();
+
+ /**
+ * Determines whether a new reconciliation will be triggered right after the current
+ * reconciliation is finished. This allows to optimize certain situations, helping avoid unneeded
+ * API calls. A reconciler might, for example, skip updating the status when it's known another
+ * reconciliation is already scheduled, which would in turn trigger another status update, thus
+ * rendering the current one moot.
+ *
+ * @return {@code true} is another reconciliation is already scheduled, {@code false} otherwise
+ **/
+ boolean isNextReconciliationImminent();
+
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java
index c064e669e0..49cf56c1aa 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java
@@ -7,11 +7,9 @@
import java.lang.annotation.Target;
import io.fabric8.kubernetes.client.informers.cache.ItemStore;
-import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;
import io.javaoperatorsdk.operator.processing.event.rate.LinearRateLimiter;
import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter;
import io.javaoperatorsdk.operator.processing.event.source.cache.BoundedItemStore;
-import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;
import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter;
import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter;
import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter;
@@ -64,19 +62,6 @@
*/
String labelSelector() default Constants.NO_VALUE_SET;
- /**
- * @deprecated Use onAddFilter, onUpdateFilter instead.
- *
- *
- * Resource event filters only applies on events of the main custom resource. Not on
- * events from other event sources nor the periodic events.
- *
- *
- * @return the list of event filters.
- */
- @Deprecated(forRemoval = true)
- Class extends ResourceEventFilter>[] eventFilters() default {};
-
/**
* Filter of onAdd events of resources.
*
@@ -107,15 +92,6 @@
MaxReconciliationInterval maxReconciliationInterval() default @MaxReconciliationInterval(
interval = MaxReconciliationInterval.DEFAULT_INTERVAL);
-
- /**
- * Optional list of {@link Dependent} configurations which associate a resource type to a
- * {@link io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource} implementation
- *
- * @return the array of {@link Dependent} configurations
- */
- Dependent[] dependents() default {};
-
/**
* Optional {@link Retry} implementation for the associated controller to use.
*
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java
index 2b0f20ef33..9ff7ddd7a3 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java
@@ -9,10 +9,11 @@
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
-import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DefaultManagedDependentResourceContext;
-import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.ManagedDependentResourceContext;
+import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DefaultManagedWorkflowAndDependentResourceContext;
+import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.ManagedWorkflowAndDependentResourceContext;
import io.javaoperatorsdk.operator.processing.Controller;
import io.javaoperatorsdk.operator.processing.event.EventSourceRetriever;
+import io.javaoperatorsdk.operator.processing.event.ResourceID;
public class DefaultContext implements Context
{
@@ -20,14 +21,15 @@ public class DefaultContext
implements Context
{
private final Controller
controller;
private final P primaryResource;
private final ControllerConfiguration
controllerConfiguration;
- private final DefaultManagedDependentResourceContext defaultManagedDependentResourceContext;
+ private final DefaultManagedWorkflowAndDependentResourceContext
defaultManagedDependentResourceContext;
public DefaultContext(RetryInfo retryInfo, Controller
controller, P primaryResource) {
this.retryInfo = retryInfo;
this.controller = controller;
this.primaryResource = primaryResource;
this.controllerConfiguration = controller.getConfiguration();
- this.defaultManagedDependentResourceContext = new DefaultManagedDependentResourceContext();
+ this.defaultManagedDependentResourceContext =
+ new DefaultManagedWorkflowAndDependentResourceContext<>(controller, primaryResource, this);
}
@Override
@@ -45,6 +47,12 @@ public IndexedResourceCache
getPrimaryCache() {
return controller.getEventSourceManager().getControllerResourceEventSource();
}
+ @Override
+ public boolean isNextReconciliationImminent() {
+ return controller.getEventProcessor()
+ .isNextReconciliationImminent(ResourceID.fromResource(primaryResource));
+ }
+
@Override
public Stream getSecondaryResourcesAsStream(Class expectedType) {
return controller.getEventSourceManager().getResourceEventSourcesFor(expectedType).stream()
@@ -72,7 +80,7 @@ public ControllerConfiguration getControllerConfiguration() {
}
@Override
- public ManagedDependentResourceContext managedDependentResourceContext() {
+ public ManagedWorkflowAndDependentResourceContext managedWorkflowAndDependentResourceContext() {
return defaultManagedDependentResourceContext;
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceInitializer.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceUtils.java
similarity index 69%
rename from operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceInitializer.java
rename to operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceUtils.java
index 09c1687e1e..8b89d95b71 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceInitializer.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceUtils.java
@@ -8,24 +8,7 @@
import io.javaoperatorsdk.operator.processing.event.source.EventSource;
import io.javaoperatorsdk.operator.processing.event.source.ResourceEventSource;
-/**
- * An interface that a {@link Reconciler} can implement to have the SDK register the provided
- * {@link EventSource}
- *
- * @param
the primary resource type handled by the associated {@link Reconciler}
- */
-public interface EventSourceInitializer
{
-
- /**
- * Prepares a map of {@link EventSource} implementations keyed by the name with which they need to
- * be registered by the SDK.
- *
- * @param context a {@link EventSourceContext} providing access to information useful to event
- * sources
- * @return a map of event sources to register
- */
- Map prepareEventSources(EventSourceContext context);
-
+public class EventSourceUtils {
/**
* Utility method to easily create map with generated name for event sources. This is for the use
* case when the event sources are not access explicitly by name in the reconciler.
@@ -33,7 +16,7 @@ public interface EventSourceInitializer
{
* @param eventSources to name
* @return even source with default names
*/
- static Map nameEventSources(EventSource... eventSources) {
+ public static Map nameEventSources(EventSource... eventSources) {
Map eventSourceMap = new HashMap<>(eventSources.length);
for (EventSource eventSource : eventSources) {
eventSourceMap.put(generateNameFor(eventSource), eventSource);
@@ -42,7 +25,7 @@ static Map nameEventSources(EventSource... eventSources) {
}
@SuppressWarnings("unchecked")
- static Map eventSourcesFromWorkflow(
+ public static Map eventSourcesFromWorkflow(
EventSourceContext context,
Workflow workflow) {
Map result = new HashMap<>();
@@ -54,13 +37,13 @@ static Map eventSourcesFromWorkflow
}
@SuppressWarnings("rawtypes")
- static Map nameEventSourcesFromDependentResource(
+ public static Map nameEventSourcesFromDependentResource(
EventSourceContext context, DependentResource... dependentResources) {
return nameEventSourcesFromDependentResource(context, Arrays.asList(dependentResources));
}
@SuppressWarnings("unchecked,rawtypes")
- static Map nameEventSourcesFromDependentResource(
+ public static Map nameEventSourcesFromDependentResource(
EventSourceContext context, Collection dependentResources) {
if (dependentResources != null) {
@@ -81,9 +64,8 @@ static Map nameEventSourcesFromDepe
* @param eventSource EventSource
* @return generated name
*/
- static String generateNameFor(EventSource eventSource) {
+ public static String generateNameFor(EventSource eventSource) {
// we can have multiple event sources for the same class
return eventSource.getClass().getName() + "#" + eventSource.hashCode();
}
-
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Reconciler.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Reconciler.java
index 55df9d1cea..2047762c35 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Reconciler.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Reconciler.java
@@ -1,8 +1,11 @@
package io.javaoperatorsdk.operator.api.reconciler;
+import java.util.*;
+
import io.fabric8.kubernetes.api.model.HasMetadata;
+import io.javaoperatorsdk.operator.processing.event.source.EventSource;
-public interface Reconciler {
+public interface Reconciler {
/**
* The implementation of this operation is required to be idempotent. Always use the UpdateControl
@@ -14,6 +17,19 @@ public interface Reconciler {
* @return UpdateControl to manage updates on the custom resource (usually the status) after
* reconciliation.
*/
- UpdateControl reconcile(R resource, Context context) throws Exception;
+ UpdateControl reconcile(P resource, Context
context) throws Exception;
+
+
+ /**
+ * Prepares a map of {@link EventSource} implementations keyed by the name with which they need to
+ * be registered by the SDK.
+ *
+ * @param context a {@link EventSourceContext} providing access to information useful to event
+ * sources
+ * @return a map of event sources to register
+ */
+ default Map prepareEventSources(EventSourceContext context) {
+ return Map.of();
+ }
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Workflow.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Workflow.java
new file mode 100644
index 0000000000..04a5b21606
--- /dev/null
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Workflow.java
@@ -0,0 +1,22 @@
+package io.javaoperatorsdk.operator.api.reconciler;
+
+import java.lang.annotation.*;
+
+import io.fabric8.kubernetes.api.model.HasMetadata;
+import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;
+
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+public @interface Workflow {
+
+ Dependent[] dependents();
+
+ /**
+ * If true, managed workflow should be explicitly invoked within the reconciler implementation. If
+ * false workflow is invoked just before the {@link Reconciler#reconcile(HasMetadata, Context)}
+ * method.
+ */
+ boolean explicitInvocation() default false;
+
+}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResource.java
index 8230dc4cf9..eec011e5e8 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResource.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResource.java
@@ -49,6 +49,16 @@ default Optional extends ResourceEventSource> eventSource(
return Optional.empty();
}
+ /**
+ * Retrieves the secondary resource (if it exists) associated with the specified primary resource
+ * for this DependentResource.
+ *
+ * @param primary the primary resource for which we want to retrieve the secondary resource
+ * associated with this DependentResource
+ * @param context the current {@link Context} in which the operation is called
+ * @return the secondary resource or {@link Optional#empty()} if it doesn't exist
+ * @throws IllegalStateException if more than one secondary is found to match the primary resource
+ */
default Optional getSecondaryResource(P primary, Context context) {
return Optional.empty();
}
@@ -68,4 +78,9 @@ static String defaultNameFor(Class extends DependentResource> dependentResourc
default boolean isDeletable() {
return this instanceof Deleter;
}
+
+ default String name() {
+ return defaultNameFor(getClass());
+ }
+
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/NameSetter.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/NameSetter.java
new file mode 100644
index 0000000000..952bf14490
--- /dev/null
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/NameSetter.java
@@ -0,0 +1,7 @@
+package io.javaoperatorsdk.operator.api.reconciler.dependent;
+
+public interface NameSetter {
+
+ void setName(String name);
+
+}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/DefaultManagedDependentResourceContext.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/DefaultManagedWorkflowAndDependentResourceContext.java
similarity index 50%
rename from operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/DefaultManagedDependentResourceContext.java
rename to operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/DefaultManagedWorkflowAndDependentResourceContext.java
index 5b1a21e5dd..f93f45ecf7 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/DefaultManagedDependentResourceContext.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/DefaultManagedWorkflowAndDependentResourceContext.java
@@ -3,15 +3,30 @@
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
+import io.fabric8.kubernetes.api.model.HasMetadata;
+import io.javaoperatorsdk.operator.api.reconciler.Context;
+import io.javaoperatorsdk.operator.processing.Controller;
import io.javaoperatorsdk.operator.processing.dependent.workflow.WorkflowCleanupResult;
import io.javaoperatorsdk.operator.processing.dependent.workflow.WorkflowReconcileResult;
@SuppressWarnings("rawtypes")
-public class DefaultManagedDependentResourceContext implements ManagedDependentResourceContext {
+public class DefaultManagedWorkflowAndDependentResourceContext
+ implements ManagedWorkflowAndDependentResourceContext {
+ private final ConcurrentHashMap attributes = new ConcurrentHashMap();
+ private final Controller
controller;
+ private final P primaryResource;
+ private final Context
context;
private WorkflowReconcileResult workflowReconcileResult;
private WorkflowCleanupResult workflowCleanupResult;
- private final ConcurrentHashMap attributes = new ConcurrentHashMap();
+
+ public DefaultManagedWorkflowAndDependentResourceContext(Controller
controller,
+ P primaryResource,
+ Context
context) {
+ this.controller = controller;
+ this.primaryResource = primaryResource;
+ this.context = context;
+ }
@Override
public Optional get(Object key, Class expectedType) {
@@ -37,25 +52,42 @@ public T getMandatory(Object key, Class expectedType) {
+ ") is missing or not of the expected type"));
}
- public DefaultManagedDependentResourceContext setWorkflowExecutionResult(
+ public DefaultManagedWorkflowAndDependentResourceContext setWorkflowExecutionResult(
WorkflowReconcileResult workflowReconcileResult) {
this.workflowReconcileResult = workflowReconcileResult;
return this;
}
- public DefaultManagedDependentResourceContext setWorkflowCleanupResult(
+ public DefaultManagedWorkflowAndDependentResourceContext setWorkflowCleanupResult(
WorkflowCleanupResult workflowCleanupResult) {
this.workflowCleanupResult = workflowCleanupResult;
return this;
}
@Override
- public Optional getWorkflowReconcileResult() {
- return Optional.ofNullable(workflowReconcileResult);
+ public WorkflowReconcileResult getWorkflowReconcileResult() {
+ return workflowReconcileResult;
}
@Override
- public Optional getWorkflowCleanupResult() {
- return Optional.ofNullable(workflowCleanupResult);
+ public WorkflowCleanupResult getWorkflowCleanupResult() {
+ return workflowCleanupResult;
}
+
+ @Override
+ public void reconcileManagedWorkflow() {
+ if (!controller.isWorkflowExplicitInvocation()) {
+ throw new IllegalStateException("Workflow explicit invocation is not set.");
+ }
+ controller.reconcileManagedWorkflow(primaryResource, context);
+ }
+
+ @Override
+ public void cleanupManageWorkflow() {
+ if (!controller.isWorkflowExplicitInvocation()) {
+ throw new IllegalStateException("Workflow explicit invocation is not set.");
+ }
+ controller.cleanupManagedWorkflow(primaryResource, context);
+ }
+
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedDependentResourceContext.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedWorkflowAndDependentResourceContext.java
similarity index 72%
rename from operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedDependentResourceContext.java
rename to operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedWorkflowAndDependentResourceContext.java
index 9c5b3dddb1..f5a25a6d9c 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedDependentResourceContext.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedWorkflowAndDependentResourceContext.java
@@ -10,7 +10,7 @@
* Contextual information related to {@link DependentResource} either to retrieve the actual
* implementations to interact with them or to pass information between them and/or the reconciler
*/
-public interface ManagedDependentResourceContext {
+public interface ManagedWorkflowAndDependentResourceContext {
/**
* Retrieve a contextual object, if it exists and is of the specified expected type, associated
@@ -37,7 +37,6 @@ public interface ManagedDependentResourceContext {
* @return an Optional containing the previous value associated with the key or
* {@link Optional#empty()} if none existed
*/
- @SuppressWarnings("unchecked")
T put(Object key, T value);
/**
@@ -52,7 +51,27 @@ public interface ManagedDependentResourceContext {
@SuppressWarnings("unused")
T getMandatory(Object key, Class expectedType);
- Optional getWorkflowReconcileResult();
+ WorkflowReconcileResult getWorkflowReconcileResult();
+
+ @SuppressWarnings("unused")
+ WorkflowCleanupResult getWorkflowCleanupResult();
+
+ /**
+ * Explicitly reconcile the declared workflow for the associated
+ * {@link io.javaoperatorsdk.operator.api.reconciler.Reconciler}
+ *
+ * @throws IllegalStateException if called when explicit invocation is not requested
+ */
+ void reconcileManagedWorkflow();
+
+ /**
+ * Explicitly clean-up dependent resources in the declared workflow for the associated
+ * {@link io.javaoperatorsdk.operator.api.reconciler.Reconciler}. Note that calling this method is
+ * only needed if the associated reconciler implements the
+ * {@link io.javaoperatorsdk.operator.api.reconciler.Cleaner} interface.
+ *
+ * @throws IllegalStateException if called when explicit invocation is not requested
+ */
+ void cleanupManageWorkflow();
- Optional getWorkflowCleanupResult();
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/Controller.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/Controller.java
index 70069148c3..7ab1eca457 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/Controller.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/Controller.java
@@ -23,6 +23,7 @@
import io.javaoperatorsdk.operator.RegisteredController;
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
import io.javaoperatorsdk.operator.api.config.ExecutorServiceManager;
+import io.javaoperatorsdk.operator.api.config.workflow.WorkflowSpec;
import io.javaoperatorsdk.operator.api.monitoring.Metrics;
import io.javaoperatorsdk.operator.api.monitoring.Metrics.ControllerExecution;
import io.javaoperatorsdk.operator.api.reconciler.Cleaner;
@@ -31,14 +32,13 @@
import io.javaoperatorsdk.operator.api.reconciler.ContextInitializer;
import io.javaoperatorsdk.operator.api.reconciler.DeleteControl;
import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext;
-import io.javaoperatorsdk.operator.api.reconciler.EventSourceInitializer;
import io.javaoperatorsdk.operator.api.reconciler.Ignore;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceNotFoundException;
import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceProvider;
import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceReferencer;
-import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DefaultManagedDependentResourceContext;
+import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DefaultManagedWorkflowAndDependentResourceContext;
import io.javaoperatorsdk.operator.health.ControllerHealthInfo;
import io.javaoperatorsdk.operator.processing.dependent.workflow.Workflow;
import io.javaoperatorsdk.operator.processing.dependent.workflow.WorkflowCleanupResult;
@@ -144,12 +144,11 @@ public Map metadata() {
@Override
public UpdateControl execute() throws Exception {
initContextIfNeeded(resource, context);
- if (!managedWorkflow.isEmpty()) {
- var res = managedWorkflow.reconcile(resource, context);
- ((DefaultManagedDependentResourceContext) context.managedDependentResourceContext())
- .setWorkflowExecutionResult(res);
- res.throwAggregateExceptionIfErrorsPresent();
- }
+ configuration.getWorkflowSpec().ifPresent(ws -> {
+ if (!isWorkflowExplicitInvocation()) {
+ reconcileManagedWorkflow(resource, context);
+ }
+ });
return reconciler.reconcile(resource, context);
}
});
@@ -189,12 +188,13 @@ public Map metadata() {
public DeleteControl execute() {
initContextIfNeeded(resource, context);
WorkflowCleanupResult workflowCleanupResult = null;
- if (managedWorkflow.hasCleaner()) {
- workflowCleanupResult = managedWorkflow.cleanup(resource, context);
- ((DefaultManagedDependentResourceContext) context.managedDependentResourceContext())
- .setWorkflowCleanupResult(workflowCleanupResult);
- workflowCleanupResult.throwAggregateExceptionIfErrorsPresent();
+
+ // The cleanup is called also when explicit invocation is true, but the cleaner is not
+ // implemented
+ if (!isCleaner || !isWorkflowExplicitInvocation()) {
+ workflowCleanupResult = cleanupManagedWorkflow(resource, context);
}
+
if (isCleaner) {
var cleanupResult = ((Cleaner) reconciler).cleanup(resource, context);
if (!cleanupResult.isRemoveFinalizer()) {
@@ -230,11 +230,8 @@ private void initContextIfNeeded(P resource, Context
context) {
}
public void initAndRegisterEventSources(EventSourceContext
context) {
- if (reconciler instanceof EventSourceInitializer) {
- final var provider = (EventSourceInitializer
) this.reconciler;
- final var ownSources = provider.prepareEventSources(context);
- ownSources.forEach(eventSourceManager::registerEventSource);
- }
+ final var ownSources = this.reconciler.prepareEventSources(context);
+ ownSources.forEach(eventSourceManager::registerEventSource);
// register created event sources
final var dependentResourcesByName =
@@ -446,4 +443,32 @@ public ExecutorServiceManager getExecutorServiceManager() {
public EventSourceContext
eventSourceContext() {
return eventSourceContext;
}
+
+ public void reconcileManagedWorkflow(P primary, Context
context) {
+ if (!managedWorkflow.isEmpty()) {
+ var res = managedWorkflow.reconcile(primary, context);
+ ((DefaultManagedWorkflowAndDependentResourceContext) context
+ .managedWorkflowAndDependentResourceContext())
+ .setWorkflowExecutionResult(res);
+ res.throwAggregateExceptionIfErrorsPresent();
+ }
+ }
+
+ public WorkflowCleanupResult cleanupManagedWorkflow(P resource, Context
context) {
+ if (managedWorkflow.hasCleaner()) {
+ var workflowCleanupResult = managedWorkflow.cleanup(resource, context);
+ ((DefaultManagedWorkflowAndDependentResourceContext) context
+ .managedWorkflowAndDependentResourceContext())
+ .setWorkflowCleanupResult(workflowCleanupResult);
+ workflowCleanupResult.throwAggregateExceptionIfErrorsPresent();
+ return workflowCleanupResult;
+ } else {
+ return null;
+ }
+ }
+
+ public boolean isWorkflowExplicitInvocation() {
+ return configuration.getWorkflowSpec().map(WorkflowSpec::isExplicitInvocation)
+ .orElse(false);
+ }
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java
index ea1e020bfb..590affe617 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java
@@ -1,6 +1,7 @@
package io.javaoperatorsdk.operator.processing.dependent;
import java.util.Optional;
+import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -11,13 +12,14 @@
import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator;
import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter;
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
+import io.javaoperatorsdk.operator.api.reconciler.dependent.NameSetter;
import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult;
import io.javaoperatorsdk.operator.processing.dependent.Matcher.Result;
import io.javaoperatorsdk.operator.processing.event.ResourceID;
@Ignore
public abstract class AbstractDependentResource
- implements DependentResource {
+ implements DependentResource, NameSetter {
private static final Logger log = LoggerFactory.getLogger(AbstractDependentResource.class);
private final boolean creatable = this instanceof Creator;
@@ -29,14 +31,21 @@ public abstract class AbstractDependentResource
private ResourceDiscriminator resourceDiscriminator;
private final DependentResourceReconciler dependentResourceReconciler;
+ protected String name;
+
@SuppressWarnings({"unchecked"})
protected AbstractDependentResource() {
+ this(null);
+ }
+
+ protected AbstractDependentResource(String name) {
creator = creatable ? (Creator) this : null;
updater = updatable ? (Updater) this : null;
dependentResourceReconciler = this instanceof BulkDependentResource
? new BulkDependentResourceReconciler<>((BulkDependentResource) this)
: new SingleDependentResourceReconciler<>(this);
+ this.name = name == null ? DependentResource.defaultNameFor(this.getClass()) : name;
}
/**
@@ -89,8 +98,39 @@ protected ReconcileResult reconcile(P primary, R actualResource, Context c
@Override
public Optional getSecondaryResource(P primary, Context context) {
- return resourceDiscriminator == null ? context.getSecondaryResource(resourceType())
- : resourceDiscriminator.distinguish(resourceType(), primary, context);
+ if (resourceDiscriminator != null) {
+ return resourceDiscriminator.distinguish(resourceType(), primary, context);
+ } else {
+ var secondaryResources = context.getSecondaryResources(resourceType());
+ if (secondaryResources.isEmpty()) {
+ return Optional.empty();
+ } else {
+ return selectManagedSecondaryResource(secondaryResources, primary, context);
+ }
+ }
+ }
+
+ /**
+ * Selects the actual secondary resource matching the desired state derived from the primary
+ * resource when several resources of the same type are found in the context. This method allows
+ * for optimized implementations in subclasses since this default implementation will check each
+ * secondary candidates for equality with the specified desired state, which might end up costly.
+ *
+ * @param secondaryResources to select the target resource from
+ *
+ * @return the matching secondary resource or {@link Optional#empty()} if none matches
+ * @throws IllegalStateException if more than one candidate is found, in which case some other
+ * mechanism might be necessary to distinguish between candidate secondary resources
+ */
+ protected Optional selectManagedSecondaryResource(Set secondaryResources, P primary,
+ Context context) {
+ R desired = desired(primary, context);
+ var targetResources = secondaryResources.stream().filter(r -> r.equals(desired)).toList();
+ if (targetResources.size() > 1) {
+ throw new IllegalStateException(
+ "More than one secondary resource related to primary: " + targetResources);
+ }
+ return targetResources.isEmpty() ? Optional.empty() : Optional.of(targetResources.get(0));
}
private void throwIfNull(R desired, P primary, String descriptor) {
@@ -158,8 +198,7 @@ protected void handleDelete(P primary, R secondary, Context
context) {
"handleDelete method must be implemented if Deleter trait is supported");
}
- public void setResourceDiscriminator(
- ResourceDiscriminator resourceDiscriminator) {
+ public void setResourceDiscriminator(ResourceDiscriminator resourceDiscriminator) {
this.resourceDiscriminator = resourceDiscriminator;
}
@@ -176,4 +215,13 @@ protected boolean isUpdatable() {
public boolean isDeletable() {
return deletable;
}
+
+ @Override
+ public String name() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractEventSourceHolderDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractEventSourceHolderDependentResource.java
index 6562afd09f..0b9f2ae897 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractEventSourceHolderDependentResource.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractEventSourceHolderDependentResource.java
@@ -31,6 +31,11 @@ public abstract class AbstractEventSourceHolderDependentResource resourceType) {
+ this(resourceType, null);
+ }
+
+ protected AbstractEventSourceHolderDependentResource(Class resourceType, String name) {
+ super(name);
this.resourceType = resourceType;
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcher.java
index c71da5d5ad..05ee05b036 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcher.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcher.java
@@ -58,7 +58,7 @@ static Matcher matcherFor(
@Override
public Result match(R actualResource, P primary, Context context) {
var desired = dependentResource.desired(primary, context);
- return match(desired, actualResource, false, false, false, context);
+ return match(desired, actualResource, false, false, context);
}
/**
@@ -67,9 +67,6 @@ public Result match(R actualResource, P primary, Context context) {
*
* @param desired the desired resource
* @param actualResource the actual resource
- * @param considerLabelsAndAnnotations {@code true} if labels and annotations will be checked for
- * equality, {@code false} otherwise (meaning that metadata changes will be ignored for
- * matching purposes)
* @param labelsAndAnnotationsEquality if true labels and annotation match exactly in the actual
* and desired state if false, additional elements are allowed in actual annotations.
* Considered only if considerLabelsAndAnnotations is true.
@@ -89,9 +86,9 @@ public Result match(R actualResource, P primary, Context context) {
*/
public static Result match(R desired,
R actualResource,
- boolean considerLabelsAndAnnotations, boolean labelsAndAnnotationsEquality,
+ boolean labelsAndAnnotationsEquality,
boolean valuesEquality, Context context) {
- return match(desired, actualResource, considerLabelsAndAnnotations,
+ return match(desired, actualResource,
labelsAndAnnotationsEquality, valuesEquality, context, EMPTY_ARRAY);
}
@@ -101,9 +98,6 @@ public static Result match(R d
*
* @param desired the desired resource
* @param actualResource the actual resource
- * @param considerLabelsAndAnnotations {@code true} if labels and annotations will be checked for
- * equality, {@code false} otherwise (meaning that metadata changes will be ignored for
- * matching purposes)
* @param labelsAndAnnotationsEquality if true labels and annotation match exactly in the actual
* and desired state if false, additional elements are allowed in actual annotations.
* Considered only if considerLabelsAndAnnotations is true.
@@ -116,9 +110,9 @@ public static Result match(R d
*/
public static Result match(R desired,
R actualResource,
- boolean considerLabelsAndAnnotations, boolean labelsAndAnnotationsEquality,
+ boolean labelsAndAnnotationsEquality,
Context context, String... ignorePaths) {
- return match(desired, actualResource, considerLabelsAndAnnotations,
+ return match(desired, actualResource,
labelsAndAnnotationsEquality, false, context, ignorePaths);
}
@@ -133,9 +127,6 @@ public static Result match(R d
* matches the desired state or not
* @param primary the primary resource from which we want to compute the desired state
* @param context the {@link Context} instance within which this method is called
- * @param considerLabelsAndAnnotations {@code true} to consider the metadata of the actual
- * resource when determining if it matches the desired state, {@code false} if matching
- * should occur only considering the spec of the resources
* @param labelsAndAnnotationsEquality if true labels and annotation match exactly in the actual
* and desired state if false, additional elements are allowed in actual annotations.
* Considered only if considerLabelsAndAnnotations is true.
@@ -150,28 +141,28 @@ public static Result match(R d
*/
public static Result match(
KubernetesDependentResource dependentResource, R actualResource, P primary,
- Context context, boolean considerLabelsAndAnnotations,
+ Context
context,
boolean labelsAndAnnotationsEquality,
String... ignorePaths) {
final var desired = dependentResource.desired(primary, context);
- return match(desired, actualResource, considerLabelsAndAnnotations,
+ return match(desired, actualResource,
labelsAndAnnotationsEquality, context,
ignorePaths);
}
public static Result match(
KubernetesDependentResource dependentResource, R actualResource, P primary,
- Context context, boolean considerLabelsAndAnnotations,
+ Context
context,
+ boolean specEquality,
boolean labelsAndAnnotationsEquality,
- boolean specEquality) {
+ String... ignorePaths) {
final var desired = dependentResource.desired(primary, context);
- return match(desired, actualResource, considerLabelsAndAnnotations,
- labelsAndAnnotationsEquality, specEquality, context);
+ return match(desired, actualResource,
+ labelsAndAnnotationsEquality, specEquality, context, ignorePaths);
}
public static Result match(R desired,
- R actualResource,
- boolean considerMetadata, boolean labelsAndAnnotationsEquality, boolean valuesEquality,
+ R actualResource, boolean labelsAndAnnotationsEquality, boolean valuesEquality,
Context context,
String... ignoredPaths) {
final List ignoreList =
@@ -195,8 +186,7 @@ public static Result match(R d
matched = match(valuesEquality, node, ignoreList);
} else if (nodeIsChildOf(node, List.of(METADATA))) {
// conditionally consider labels and annotations
- if (considerMetadata
- && nodeIsChildOf(node, List.of(METADATA_LABELS, METADATA_ANNOTATIONS))) {
+ if (nodeIsChildOf(node, List.of(METADATA_LABELS, METADATA_ANNOTATIONS))) {
matched = match(labelsAndAnnotationsEquality, node, Collections.emptyList());
}
} else if (!nodeIsChildOf(node, IGNORED_FIELDS)) {
@@ -227,20 +217,4 @@ static String getPath(JsonNode n) {
return n.get(PATH).asText();
}
- @Deprecated(forRemoval = true)
- public static Result match(
- KubernetesDependentResource dependentResource, R actualResource, P primary,
- Context context, boolean considerLabelsAndAnnotations, boolean specEquality) {
- final var desired = dependentResource.desired(primary, context);
- return match(desired, actualResource, considerLabelsAndAnnotations, specEquality, context);
- }
-
- @Deprecated(forRemoval = true)
- public static Result match(
- KubernetesDependentResource dependentResource, R actualResource, P primary,
- Context context, boolean considerLabelsAndAnnotations, String... ignorePaths) {
- final var desired = dependentResource.desired(primary, context);
- return match(desired, actualResource, considerLabelsAndAnnotations, true, context, ignorePaths);
- }
-
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java
index b804b88a30..edc7491469 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java
@@ -1,6 +1,7 @@
package io.javaoperatorsdk.operator.processing.dependent.kubernetes;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
import java.util.Set;
@@ -43,7 +44,12 @@ public abstract class KubernetesDependentResource resourceType) {
- super(resourceType);
+ this(resourceType, null);
+ }
+
+ @SuppressWarnings("unchecked")
+ public KubernetesDependentResource(Class resourceType, String name) {
+ super(resourceType, name);
usingCustomResourceUpdateMatcher = this instanceof ResourceUpdaterMatcher;
updaterMatcher = usingCustomResourceUpdateMatcher
@@ -289,6 +295,29 @@ protected void addSecondaryToPrimaryMapperAnnotations(R desired, P primary, Stri
}
}
+ @Override
+ protected Optional selectManagedSecondaryResource(Set secondaryResources, P primary,
+ Context context) {
+ ResourceID managedResourceID = managedSecondaryResourceID(primary, context);
+ return secondaryResources.stream()
+ .filter(r -> r.getMetadata().getName().equals(managedResourceID.getName()) &&
+ Objects.equals(r.getMetadata().getNamespace(),
+ managedResourceID.getNamespace().orElse(null)))
+ .findFirst();
+ }
+
+ /**
+ * Override this method in order to optimize and not compute the desired when selecting the target
+ * secondary resource. Simply, a static ResourceID can be returned.
+ *
+ * @param primary resource
+ * @param context of current reconciliation
+ * @return id of the target managed resource
+ */
+ protected ResourceID managedSecondaryResourceID(P primary, Context
context) {
+ return ResourceID.fromResource(desired(primary, context));
+ }
+
protected boolean addOwnerReference() {
return garbageCollected;
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/updatermatcher/GenericResourceUpdaterMatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/updatermatcher/GenericResourceUpdaterMatcher.java
index 2a5bae03b9..e3b999315c 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/updatermatcher/GenericResourceUpdaterMatcher.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/updatermatcher/GenericResourceUpdaterMatcher.java
@@ -41,7 +41,7 @@ public R updateResource(R actual, R desired, Context> context) {
@Override
public boolean matches(R actual, R desired, Context> context) {
- return GenericKubernetesResourceMatcher.match(desired, actual, true,
+ return GenericKubernetesResourceMatcher.match(desired, actual,
false, false, context).matched();
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutor.java
index 56e5e17d07..05b546553c 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutor.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutor.java
@@ -18,7 +18,7 @@
import io.javaoperatorsdk.operator.processing.event.ResourceID;
@SuppressWarnings("rawtypes")
-public abstract class AbstractWorkflowExecutor
{
+abstract class AbstractWorkflowExecutor
{
protected final Workflow
workflow;
protected final P primary;
@@ -133,17 +133,16 @@ protected void registerOrDeregisterEventSourceBasedOnActivation(
boolean activationConditionMet,
DependentResourceNode dependentResourceNode) {
if (dependentResourceNode.getActivationCondition().isPresent()) {
+ final var dr = dependentResourceNode.getDependentResource();
+ final var eventSourceRetriever = context.eventSourceRetriever();
if (activationConditionMet) {
var eventSource =
- dependentResourceNode.getDependentResource().eventSource(context.eventSourceRetriever()
- .eventSourceContextForDynamicRegistration());
+ dr.eventSource(eventSourceRetriever.eventSourceContextForDynamicRegistration());
var es = eventSource.orElseThrow();
- context.eventSourceRetriever()
- .dynamicallyRegisterEventSource(dependentResourceNode.getName(), es);
+ eventSourceRetriever.dynamicallyRegisterEventSource(dr.name(), es);
} else {
- context.eventSourceRetriever()
- .dynamicallyDeRegisterEventSource(dependentResourceNode.getName());
+ eventSourceRetriever.dynamicallyDeRegisterEventSource(dr.name());
}
}
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultManagedWorkflow.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultManagedWorkflow.java
index 27400230df..fb0b733c32 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultManagedWorkflow.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultManagedWorkflow.java
@@ -12,8 +12,10 @@
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceReferencer;
+import io.javaoperatorsdk.operator.api.reconciler.dependent.NameSetter;
import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.KubernetesClientAware;
+import static io.javaoperatorsdk.operator.api.reconciler.Constants.NO_VALUE_SET;
import static io.javaoperatorsdk.operator.processing.dependent.workflow.Workflow.THROW_EXCEPTION_AUTOMATICALLY_DEFAULT;
@SuppressWarnings("rawtypes")
@@ -77,13 +79,14 @@ public Workflow resolve(KubernetesClient client,
ControllerConfiguration
configuration) {
final var alreadyResolved = new HashMap(orderedSpecs.size());
for (DependentResourceSpec spec : orderedSpecs) {
- final var node = new DependentResourceNode(spec.getName(),
+ final var dependentResource = resolve(spec, client, configuration);
+ final var node = new DependentResourceNode(
spec.getReconcileCondition(),
spec.getDeletePostCondition(),
spec.getReadyCondition(),
spec.getActivationCondition(),
- resolve(spec, client, configuration));
- alreadyResolved.put(node.getName(), node);
+ dependentResource);
+ alreadyResolved.put(dependentResource.name(), node);
spec.getDependsOn()
.forEach(depend -> node.addDependsOnRelation(alreadyResolved.get(depend)));
}
@@ -104,6 +107,11 @@ private DependentResource resolve(DependentResourceSpec spec,
configuration.getConfigurationService().dependentResourceFactory()
.createFrom(spec, configuration);
+ final var name = spec.getName();
+ if (name != null && !NO_VALUE_SET.equals(name) && dependentResource instanceof NameSetter) {
+ ((NameSetter) dependentResource).setName(name);
+ }
+
if (dependentResource instanceof KubernetesClientAware) {
((KubernetesClientAware) dependentResource).setKubernetesClient(client);
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultWorkflow.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultWorkflow.java
index d31f42419d..1c241aebbc 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultWorkflow.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultWorkflow.java
@@ -20,7 +20,7 @@
* @param