Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,16 @@
package io.javaoperatorsdk.operator.api.reconciler;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
import java.util.stream.Collector;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -32,6 +40,7 @@
import io.javaoperatorsdk.operator.processing.event.ResourceID;
import io.javaoperatorsdk.operator.processing.event.source.informer.ManagedInformerEventSource;

import static io.javaoperatorsdk.operator.ReconcilerUtilsInternal.compareResourceVersions;
import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getUID;
import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getVersion;

Expand Down Expand Up @@ -364,13 +373,13 @@ public static <R extends HasMetadata> R resourcePatch(
if (esList.isEmpty()) {
throw new IllegalStateException("No event source found for type: " + resource.getClass());
}
var es = esList.get(0);
if (esList.size() > 1) {
throw new IllegalStateException(
"Multiple event sources found for: "
+ resource.getClass()
+ " please provide the target event source");
log.warn(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would technically be an API breaking change…

Copy link
Collaborator Author

@csviri csviri Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean, could you pls elaborate? We don't have this released yet.

"Multiple event sources found for type: {}, selecting first with name {}",
resource.getClass(),
es.name());
}
var es = esList.get(0);
if (es instanceof ManagedInformerEventSource mes) {
return resourcePatch(resource, updateOperation, mes);
} else {
Expand Down Expand Up @@ -595,4 +604,56 @@ public static <P extends HasMetadata> P addFinalizerWithSSA(
e);
}
}

/**
* Returns a collector that deduplicates Kubernetes objects by keeping only the one with the
* latest metadata.resourceVersion for each unique name and namespace combination. The intended
* use case is for the rather rare setup when there are overlapping {@link
* io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource}s for a
* resource type.
*
* @param <T> the type of HasMetadata objects
* @return a collector that produces a collection of deduplicated Kubernetes objects
*/
public static <T extends HasMetadata> Collector<T, ?, Collection<T>> latestDistinct() {
return Collectors.collectingAndThen(latestDistinctToMap(), Map::values);
}

/**
* Returns a collector that deduplicates Kubernetes objects by keeping only the one with the
* latest metadata.resourceVersion for each unique name and namespace combination. The intended
* use case is for the rather rare setup when there are overlapping {@link
* io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource}s for a
* resource type.
*
* @param <T> the type of HasMetadata objects
* @return a collector that produces a List of deduplicated Kubernetes objects
*/
public static <T extends HasMetadata> Collector<T, ?, List<T>> latestDistinctList() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get why this method or the set variant are needed or even useful. The semantic should be a Set since instances should be unique, no?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Set variant is needed since we are covering the use case whern there are multiple event sources for same type without distinct set of resource watched, so we returning the latest of those.

I added List just for convinience. If somebody would prefer working above Lists, that is quite common I beleive, but you are right that the semantically Set is the correct one.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh additional issue with adding it as default, is that this applies only for kubernetes resources, while in getSecondaryResources handles also external resources.

return Collectors.collectingAndThen(
latestDistinctToMap(), map -> new ArrayList<>(map.values()));
}

/**
* Returns a collector that deduplicates Kubernetes objects by keeping only the one with the
* latest metadata.resourceVersion for each unique name and namespace combination. The intended
* use case is for the rather rare setup when there are overlapping {@link
* io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource}s for a
* resource type.
*
* @param <T> the type of HasMetadata objects
* @return a collector that produces a Set of deduplicated Kubernetes objects
*/
public static <T extends HasMetadata> Collector<T, ?, Set<T>> latestDistinctSet() {
return Collectors.collectingAndThen(latestDistinctToMap(), map -> new HashSet<>(map.values()));
}

private static <T extends HasMetadata> Collector<T, ?, Map<ResourceID, T>> latestDistinctToMap() {
return Collectors.toMap(
resource ->
new ResourceID(resource.getMetadata().getName(), resource.getMetadata().getNamespace()),
resource -> resource,
(existing, replacement) ->
compareResourceVersions(existing, replacement) >= 0 ? existing : replacement);
Copy link
Collaborator Author

@csviri csviri Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @metacosm this is the point to keep of this PR, just to keep the latest

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be better if that was done transparently by the SDK? Also, wouldn't it be better to provide a filtered stream on Context rather than a Collector implementation?

Copy link
Collaborator Author

@csviri csviri Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This cannot be implemented by as a filter AFAIK.

}
}
Loading