diff --git a/README.md b/README.md
index 86edf232b0..72eb2f7974 100644
--- a/README.md
+++ b/README.md
@@ -15,6 +15,10 @@ conversion hooks and dynamic admission controllers are supported as a separate p
Under the hood it uses the excellent [Fabric8 Kubernetes Client](https://github.com/fabric8io/kubernetes-client),
which provides additional facilities, like generating CRD from source code (and vice versa).
+
+
+Java Operator SDK is a CNCF project as part of [Operator Framework](https://github.com/operator-framework).
+
## Documentation
Documentation can be found on the **[JOSDK WebSite](https://javaoperatorsdk.io/)**.
@@ -63,6 +67,7 @@ projects want to advertise that fact here. For this reason, we ask that if you'd
to be featured in this section, please open a PR, adding a link to and short description of your
project, as shown below:
+- [kroxylicious](https://github.com/kroxylicious/kroxylicious/tree/main/kroxylicious-operator) Kafka proxy operator
- [ExposedApp operator](https://github.com/halkyonio/exposedapp-rhdblog): a sample operator
written to illustrate JOSDK concepts and its Quarkus extension in the ["Write Kubernetes
Operators in Java with the Java Operator SDK" blog series](https://developers.redhat.com/articles/2022/02/15/write-kubernetes-java-java-operator-sdk#).
diff --git a/bootstrapper-maven-plugin/pom.xml b/bootstrapper-maven-plugin/pom.xml
index 7487daa6fd..eaa32eb009 100644
--- a/bootstrapper-maven-plugin/pom.xml
+++ b/bootstrapper-maven-plugin/pom.xml
@@ -5,7 +5,7 @@
io.javaoperatorsdk
java-operator-sdk
- 5.1.1-SNAPSHOT
+ 5.1.2-SNAPSHOT
bootstrapper
diff --git a/caffeine-bounded-cache-support/pom.xml b/caffeine-bounded-cache-support/pom.xml
index a421b3cd9f..c45e56386d 100644
--- a/caffeine-bounded-cache-support/pom.xml
+++ b/caffeine-bounded-cache-support/pom.xml
@@ -4,7 +4,7 @@
io.javaoperatorsdk
java-operator-sdk
- 5.1.1-SNAPSHOT
+ 5.1.2-SNAPSHOT
caffeine-bounded-cache-support
diff --git a/docs/content/en/blog/news/primary-cache-for-next-recon.md b/docs/content/en/blog/news/primary-cache-for-next-recon.md
new file mode 100644
index 0000000000..67326a6f17
--- /dev/null
+++ b/docs/content/en/blog/news/primary-cache-for-next-recon.md
@@ -0,0 +1,92 @@
+---
+title: How to guarantee allocated values for next reconciliation
+date: 2025-05-22
+author: >-
+ [Attila Mészáros](https://github.com/csviri) and [Chris Laprun](https://github.com/metacosm)
+---
+
+We recently released v5.1 of Java Operator SDK (JOSDK). One of the highlights of this release is related to a topic of
+so-called
+[allocated values](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#representing-allocated-values
+).
+
+To describe the problem, let's say that our controller needs to create a resource that has a generated identifier, i.e.
+a resource which identifier cannot be directly derived from the custom resource's desired state as specified in its
+`spec` field. To record the fact that the resource was successfully created, and to avoid attempting to
+recreate the resource again in subsequent reconciliations, it is typical for this type of controller to store the
+generated identifier in the custom resource's `status` field.
+
+The Java Operator SDK relies on the informers' cache to retrieve resources. These caches, however, are only guaranteed
+to be eventually consistent. It could happen that, if some other event occurs, that would result in a new
+reconciliation, **before** the update that's been made to our resource status has the chance to be propagated first to
+the cluster and then back to the informer cache, that the resource in the informer cache does **not** contain the latest
+version as modified by the reconciler. This would result in a new reconciliation where the generated identifier would be
+missing from the resource status and, therefore, another attempt to create the resource by the reconciler, which is not
+what we'd like.
+
+Java Operator SDK now provides a utility class [
+`PrimaryUpdateAndCacheUtils`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/PrimaryUpdateAndCacheUtils.java)
+to handle this particular use case. Using that overlay cache, your reconciler is guaranteed to see the most up-to-date
+version of the resource on the next reconciliation:
+
+```java
+
+@Override
+public UpdateControl reconcile(
+ StatusPatchCacheCustomResource resource,
+ Context context) {
+
+ // omitted code
+
+ var freshCopy = createFreshCopy(resource); // need fresh copy just because we use the SSA version of update
+ freshCopy
+ .getStatus()
+ .setValue(statusWithAllocatedValue());
+
+ // using the utility instead of update control to patch the resource status
+ var updated =
+ PrimaryUpdateAndCacheUtils.ssaPatchStatusAndCacheResource(resource, freshCopy, context);
+ return UpdateControl.noUpdate();
+}
+```
+
+How does `PrimaryUpdateAndCacheUtils` work?
+There are multiple ways to solve this problem, but ultimately, we only provide the solution described below. If you
+want to dig deep in alternatives, see
+this [PR](https://github.com/operator-framework/java-operator-sdk/pull/2800/files).
+
+The trick is to intercept the resource that the reconciler updated and cache that version in an additional cache on top
+of the informer's cache. Subsequently, if the reconciler needs to read the resource, the SDK will first check if it is
+in the overlay cache and read it from there if present, otherwise read it from the informer's cache. If the informer
+receives an event with a fresh resource, we always remove the resource from the overlay cache, since that is a more
+recent resource. But this **works only** if the reconciler updates the resource using **optimistic locking**.
+If the update fails on conflict, because the resource has already been updated on the cluster before we got
+the chance to get our update in, we simply wait and poll the informer cache until the new resource version from the
+server appears in the informer's cache,
+and then try to apply our updates to the resource again using the updated version from the server, again with optimistic
+locking.
+
+So why is optimistic locking required? We hinted at it above, but the gist of it, is that if another party updates the
+resource before we get a chance to, we wouldn't be able to properly handle the resulting situation correctly in all
+cases. The informer would receive that new event before our own update would get a chance to propagate. Without
+optimistic locking, there wouldn't be a fail-proof way to determine which update should prevail (i.e. which occurred
+first), in particular in the event of the informer losing the connection to the cluster or other edge cases (the joys of
+distributed computing!).
+
+Optimistic locking simplifies the situation and provides us with stronger guarantees: if the update succeeds, then we
+can be sure we have the proper resource version in our caches. The next event will contain our update in all cases.
+Because we know that, we can also be sure that we can evict the cached resource in the overlay cache whenever we receive
+a new event. The overlay cache is only used if the SDK detects that the original resource (i.e. the one before we
+applied our status update in the example above) is still in the informer's cache.
+
+The following diagram sums up the process:
+
+```mermaid
+flowchart TD
+ A["Update Resource with Lock"] --> B{"Is Successful"}
+ B -- Fails on conflict --> D["Poll the Informer cache until resource updated"]
+ D --> A
+ B -- Yes --> n2{"Original resource still in informer cache?"}
+ n2 -- Yes --> C["Cache the resource in overlay cache"]
+ n2 -- No --> n3["Informer cache already contains up-to-date version, do not use overlay cache"]
+```
diff --git a/docs/content/en/docs/documentation/dependent-resource-and-workflows/dependent-resources.md b/docs/content/en/docs/documentation/dependent-resource-and-workflows/dependent-resources.md
index 304e20bafe..43b7f37364 100644
--- a/docs/content/en/docs/documentation/dependent-resource-and-workflows/dependent-resources.md
+++ b/docs/content/en/docs/documentation/dependent-resource-and-workflows/dependent-resources.md
@@ -133,7 +133,7 @@ Deleted (or set to be garbage collected). The following example shows how to cre
```java
-@KubernetesDependent(labelSelector = WebPageManagedDependentsReconciler.SELECTOR)
+@KubernetesDependent(informer = @Informer(labelSelector = SELECTOR))
class DeploymentDependentResource extends CRUDKubernetesDependentResource {
@Override
@@ -174,7 +174,8 @@ JOSDK will take the appropriate steps to wire everything together and call your
`DependentResource` implementations `reconcile` method before your primary resource is reconciled.
This makes sense in most use cases where the logic associated with the primary resource is
usually limited to status handling based on the state of the secondary resources and the
-resources are not dependent on each other.
+resources are not dependent on each other. As an alternative, you can also invoke reconciliation explicitly,
+event for managed workflows.
See [Workflows](https://javaoperatorsdk.io/docs/workflows) for more details on how the dependent
resources are reconciled.
@@ -184,12 +185,14 @@ instances are managed by JOSDK, an example of which can be seen below:
```java
-@ControllerConfiguration(
- labelSelector = SELECTOR,
+@Workflow(
dependents = {
@Dependent(type = ConfigMapDependentResource.class),
@Dependent(type = DeploymentDependentResource.class),
- @Dependent(type = ServiceDependentResource.class)
+ @Dependent(type = ServiceDependentResource.class),
+ @Dependent(
+ type = IngressDependentResource.class,
+ reconcilePrecondition = ExposedIngressCondition.class)
})
public class WebPageManagedDependentsReconciler
implements Reconciler, ErrorStatusHandler {
@@ -204,7 +207,6 @@ public class WebPageManagedDependentsReconciler
webPage.setStatus(createStatus(name));
return UpdateControl.patchStatus(webPage);
}
-
}
```
diff --git a/docs/content/en/docs/faq/_index.md b/docs/content/en/docs/faq/_index.md
index 9308ce4cfa..1c3d82fe35 100644
--- a/docs/content/en/docs/faq/_index.md
+++ b/docs/content/en/docs/faq/_index.md
@@ -90,8 +90,38 @@ reconciliation. For example if you patch the status at the end of the reconcilia
it is not guaranteed that during the next reconciliation you will see the fresh resource. Therefore, controllers
which do this, usually cache the updated status in memory to make sure it is present for next reconciliation.
-Dependent Resources feature supports the [first approach](../dependent-resources/_index.md#external-state-tracking-dependent-resources).
+From version 5.1 you can use [this utility](../documentation/reconciler.md#making-sure-the-primary-resource-is-up-to-date-for-the-next-reconciliation)
+to make sure an updated status is present for the next reconciliation.
+
+Dependent Resources feature supports the [first approach](../documentation/dependent-resource-and-workflows/dependent-resources.md#external-state-tracking-dependent-resources).
+### How can I make the status update of my custom resource trigger a reconciliation?
+
+The framework checks, by default, when an event occurs, that could trigger a reconciliation, if the event increased the
+`generation` field of the primary resource's metadata and filters out the event if it did not. `generation` is typically
+only increased when the `.spec` field of a resource is changed. As a result, a change in the `.status` field would not
+normally trigger a reconciliation.
+
+To change this behavior, you can set the [
+`generationAwareEventProcessing`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java#L43)
+to `false`:
+
+```java
+
+@ControllerConfiguration(generationAwareEventProcessing = false)
+static class TestCustomReconciler implements Reconciler {
+
+ @Override
+ public UpdateControl reconcile(TestCustomResource resource, Context context) {
+ // code omitted
+ }
+}
+```
+
+For secondary resources, every change should trigger a reconciliation by default, except when you add explicit filters
+or use dependent resource implementations that filter out changes they trigger themselves by default,
+see [related docs](../documentation/dependent-resource-and-workflows/dependent-resources.md#caching-and-event-handling-in-kubernetesdependentresource).
+
### How can I skip the reconciliation of a dependent resource?
Skipping workflow reconciliation altogether is possible with the explicit invocation feature since v5.
diff --git a/docs/static/images/cncf_logo2.png b/docs/static/images/cncf_logo2.png
new file mode 100644
index 0000000000..e1236b7e87
Binary files /dev/null and b/docs/static/images/cncf_logo2.png differ
diff --git a/micrometer-support/pom.xml b/micrometer-support/pom.xml
index 9d9a47464f..73ed6ff77e 100644
--- a/micrometer-support/pom.xml
+++ b/micrometer-support/pom.xml
@@ -4,7 +4,7 @@
io.javaoperatorsdk
java-operator-sdk
- 5.1.1-SNAPSHOT
+ 5.1.2-SNAPSHOT
micrometer-support
diff --git a/operator-framework-bom/pom.xml b/operator-framework-bom/pom.xml
index 123ae032c7..ccbb4ca4e2 100644
--- a/operator-framework-bom/pom.xml
+++ b/operator-framework-bom/pom.xml
@@ -4,7 +4,7 @@
io.javaoperatorsdk
operator-framework-bom
- 5.1.1-SNAPSHOT
+ 5.1.2-SNAPSHOT
pom
Operator SDK - Bill of Materials
Java SDK for implementing Kubernetes operators
diff --git a/operator-framework-core/pom.xml b/operator-framework-core/pom.xml
index b4166a3761..69c02d6f01 100644
--- a/operator-framework-core/pom.xml
+++ b/operator-framework-core/pom.xml
@@ -4,7 +4,7 @@
io.javaoperatorsdk
java-operator-sdk
- 5.1.1-SNAPSHOT
+ 5.1.2-SNAPSHOT
../pom.xml
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/RuntimeInfo.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/RuntimeInfo.java
index b7fbce7f07..0495131d79 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/RuntimeInfo.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/RuntimeInfo.java
@@ -5,6 +5,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.health.EventSourceHealthIndicator;
import io.javaoperatorsdk.operator.health.InformerWrappingEventSourceHealthIndicator;
import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerEventSource;
@@ -22,7 +23,7 @@ public class RuntimeInfo {
private final Operator operator;
public RuntimeInfo(Operator operator) {
- this.registeredControllers = operator.getRegisteredControllers();
+ this.registeredControllers = Collections.unmodifiableSet(operator.getRegisteredControllers());
this.operator = operator;
}
@@ -30,6 +31,7 @@ public boolean isStarted() {
return operator.isStarted();
}
+ @SuppressWarnings("unused")
public Set getRegisteredControllers() {
checkIfStarted();
return registeredControllers;
@@ -80,4 +82,23 @@ public Map> unhealthyEventSource
}
return res;
}
+
+ /**
+ * Retrieves the {@link RegisteredController} associated with the specified controller name or
+ * {@code null} if no such controller is registered.
+ *
+ * @param controllerName the name of the {@link RegisteredController} to retrieve
+ * @return the {@link RegisteredController} associated with the specified controller name or
+ * {@code null} if no such controller is registered
+ * @since 5.1.2
+ */
+ @SuppressWarnings({"unchecked", "unused"})
+ public RegisteredController extends HasMetadata> getRegisteredController(
+ String controllerName) {
+ checkIfStarted();
+ return registeredControllers.stream()
+ .filter(rc -> rc.getConfiguration().getName().equals(controllerName))
+ .findFirst()
+ .orElse(null);
+ }
}
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 18e74d29a9..864b65c3f7 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
@@ -28,7 +28,6 @@
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfig;
-import io.javaoperatorsdk.operator.processing.dependent.kubernetes.ResourceUpdaterMatcher;
import io.javaoperatorsdk.operator.processing.dependent.workflow.ManagedWorkflowFactory;
import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerEventSource;
@@ -396,9 +395,6 @@ default boolean shouldUseSSA(
Class extends KubernetesDependentResource> dependentResourceType,
Class extends HasMetadata> resourceType,
KubernetesDependentResourceConfig extends HasMetadata> config) {
- if (ResourceUpdaterMatcher.class.isAssignableFrom(dependentResourceType)) {
- return false;
- }
Boolean useSSAConfig =
Optional.ofNullable(config).map(KubernetesDependentResourceConfig::useSSA).orElse(null);
// don't use SSA for certain resources by default, only if explicitly overridden
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/ResourceUpdaterMatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/ResourceUpdaterMatcher.java
deleted file mode 100644
index d893ff3e86..0000000000
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/ResourceUpdaterMatcher.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package io.javaoperatorsdk.operator.processing.dependent.kubernetes;
-
-import io.fabric8.kubernetes.api.model.HasMetadata;
-import io.javaoperatorsdk.operator.api.reconciler.Context;
-
-public interface ResourceUpdaterMatcher {
-
- R updateResource(R actual, R desired, Context> context);
-
- boolean matches(R actual, R desired, Context> context);
-}
diff --git a/operator-framework-junit5/pom.xml b/operator-framework-junit5/pom.xml
index 111fdf3b1b..07bfa9cd1c 100644
--- a/operator-framework-junit5/pom.xml
+++ b/operator-framework-junit5/pom.xml
@@ -4,7 +4,7 @@
io.javaoperatorsdk
java-operator-sdk
- 5.1.1-SNAPSHOT
+ 5.1.2-SNAPSHOT
operator-framework-junit-5
diff --git a/operator-framework/pom.xml b/operator-framework/pom.xml
index 0d60152818..18cbda43cf 100644
--- a/operator-framework/pom.xml
+++ b/operator-framework/pom.xml
@@ -4,7 +4,7 @@
io.javaoperatorsdk
java-operator-sdk
- 5.1.1-SNAPSHOT
+ 5.1.2-SNAPSHOT
operator-framework
diff --git a/pom.xml b/pom.xml
index 6bc27adeed..0162bb0849 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
io.javaoperatorsdk
java-operator-sdk
- 5.1.1-SNAPSHOT
+ 5.1.2-SNAPSHOT
pom
Operator SDK for Java
Java SDK for implementing Kubernetes operators
@@ -59,11 +59,10 @@
java-operator-sdk
https://sonarcloud.io
jdk
-
- 5.12.2
+ 5.13.2
7.3.1
- 2.0.12
- 2.24.3
+ 2.0.17
+ 2.25.0
5.18.0
3.17.0
0.21.0
@@ -71,8 +70,8 @@
3.27.3
4.3.0
2.7.3
- 1.15.0
- 3.2.0
+ 1.15.1
+ 3.2.1
0.9.14
2.19.0
4.15
@@ -84,14 +83,14 @@
3.3.1
3.3.1
3.4.2
- 3.4.1
+ 3.5.0
3.2.7
1.7.0
3.0.0
3.1.4
9.0.2
- 3.4.5
- 2.44.4
+ 3.4.6
+ 2.44.5
diff --git a/sample-operators/controller-namespace-deletion/pom.xml b/sample-operators/controller-namespace-deletion/pom.xml
index 75ef1ee5eb..9a87338da5 100644
--- a/sample-operators/controller-namespace-deletion/pom.xml
+++ b/sample-operators/controller-namespace-deletion/pom.xml
@@ -5,7 +5,7 @@
io.javaoperatorsdk
sample-operators
- 5.1.1-SNAPSHOT
+ 5.1.2-SNAPSHOT
sample-controller-namespace-deletion
diff --git a/sample-operators/leader-election/pom.xml b/sample-operators/leader-election/pom.xml
index 5611726540..ca74158ae6 100644
--- a/sample-operators/leader-election/pom.xml
+++ b/sample-operators/leader-election/pom.xml
@@ -5,7 +5,7 @@
io.javaoperatorsdk
sample-operators
- 5.1.1-SNAPSHOT
+ 5.1.2-SNAPSHOT
sample-leader-election
diff --git a/sample-operators/mysql-schema/pom.xml b/sample-operators/mysql-schema/pom.xml
index 263a832d10..94b2f93769 100644
--- a/sample-operators/mysql-schema/pom.xml
+++ b/sample-operators/mysql-schema/pom.xml
@@ -5,7 +5,7 @@
io.javaoperatorsdk
sample-operators
- 5.1.1-SNAPSHOT
+ 5.1.2-SNAPSHOT
sample-mysql-schema-operator
diff --git a/sample-operators/pom.xml b/sample-operators/pom.xml
index d89c0677d6..2f1c9c1645 100644
--- a/sample-operators/pom.xml
+++ b/sample-operators/pom.xml
@@ -5,7 +5,7 @@
io.javaoperatorsdk
java-operator-sdk
- 5.1.1-SNAPSHOT
+ 5.1.2-SNAPSHOT
sample-operators
diff --git a/sample-operators/tomcat-operator/pom.xml b/sample-operators/tomcat-operator/pom.xml
index 0bc6f86eda..38d6b4ec0c 100644
--- a/sample-operators/tomcat-operator/pom.xml
+++ b/sample-operators/tomcat-operator/pom.xml
@@ -5,7 +5,7 @@
io.javaoperatorsdk
sample-operators
- 5.1.1-SNAPSHOT
+ 5.1.2-SNAPSHOT
sample-tomcat-operator
diff --git a/sample-operators/webpage/pom.xml b/sample-operators/webpage/pom.xml
index d2bfacc70e..7f118be1bb 100644
--- a/sample-operators/webpage/pom.xml
+++ b/sample-operators/webpage/pom.xml
@@ -5,7 +5,7 @@
io.javaoperatorsdk
sample-operators
- 5.1.1-SNAPSHOT
+ 5.1.2-SNAPSHOT
sample-webpage-operator