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 c27b13714e..5fd908d56e 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 @@ -321,6 +321,8 @@ private

ResolvedControllerConfiguration

controllerCon var triggerReconcilerOnAllEvents = annotation != null && annotation.triggerReconcilerOnAllEvents(); + var withoutDefaultFilters = annotation != null && annotation.withoutDefaultFilters(); + InformerConfiguration

informerConfig = InformerConfiguration.builder(resourceClass) .initFromAnnotation(annotation != null ? annotation.informer() : null, context) @@ -341,7 +343,8 @@ private

ResolvedControllerConfiguration

controllerCon dependentFieldManager, this, informerConfig, - triggerReconcilerOnAllEvents); + triggerReconcilerOnAllEvents, + withoutDefaultFilters); } /** 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 63177b614f..73059c985e 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 @@ -121,4 +121,8 @@ default boolean triggerReconcilerOnAllEvent() { default boolean triggerReconcilerOnAllEvents() { return false; } + + default boolean isWithoutDefaultFilters() { + return false; + } } 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 7856654f1e..d0685e5fc3 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 @@ -46,6 +46,7 @@ public class ControllerConfigurationOverrider { private Map configurations; private final InformerConfiguration.Builder config; private boolean triggerReconcilerOnAllEvents; + private boolean withoutDefaultFilters; private ControllerConfigurationOverrider(ControllerConfiguration original) { this.finalizer = original.getFinalizerName(); @@ -59,6 +60,7 @@ private ControllerConfigurationOverrider(ControllerConfiguration original) { this.name = original.getName(); this.fieldManager = original.fieldManager(); this.triggerReconcilerOnAllEvents = original.triggerReconcilerOnAllEvents(); + this.withoutDefaultFilters = original.isWithoutDefaultFilters(); } public ControllerConfigurationOverrider withFinalizer(String finalizer) { @@ -186,6 +188,11 @@ public ControllerConfigurationOverrider withTriggerReconcilerOnAllEvents( return this; } + public ControllerConfigurationOverrider withoutDefaultFilters(boolean withoutDefaultFilters) { + this.withoutDefaultFilters = withoutDefaultFilters; + return this; + } + /** * Sets a max page size limit when starting the informer. This will result in pagination while * populating the cache. This means that longer lists will take multiple requests to fetch. See @@ -231,6 +238,7 @@ public ControllerConfiguration build() { original.getConfigurationService(), config.buildForController(), triggerReconcilerOnAllEvents, + withoutDefaultFilters, original.getWorkflowSpec().orElse(null)); } 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 3e620f8f91..d8a19c1194 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 @@ -45,6 +45,7 @@ public class ResolvedControllerConfiguration

private final ConfigurationService configurationService; private final String fieldManager; private final boolean triggerReconcilerOnAllEvents; + private final boolean withoutDefaultFilters; private WorkflowSpec workflowSpec; public ResolvedControllerConfiguration(ControllerConfiguration

other) { @@ -61,6 +62,7 @@ public ResolvedControllerConfiguration(ControllerConfiguration

other) { other.getConfigurationService(), other.getInformerConfig(), other.triggerReconcilerOnAllEvents(), + other.isWithoutDefaultFilters(), other.getWorkflowSpec().orElse(null)); } @@ -77,6 +79,7 @@ public ResolvedControllerConfiguration( ConfigurationService configurationService, InformerConfiguration

informerConfig, boolean triggerReconcilerOnAllEvents, + boolean withoutDefaultFilters, WorkflowSpec workflowSpec) { this( name, @@ -90,7 +93,8 @@ public ResolvedControllerConfiguration( fieldManager, configurationService, informerConfig, - triggerReconcilerOnAllEvents); + triggerReconcilerOnAllEvents, + withoutDefaultFilters); setWorkflowSpec(workflowSpec); } @@ -106,7 +110,8 @@ protected ResolvedControllerConfiguration( String fieldManager, ConfigurationService configurationService, InformerConfiguration

informerConfig, - boolean triggerReconcilerOnAllEvents) { + boolean triggerReconcilerOnAllEvents, + boolean withoutDefaultFilters) { this.informerConfig = informerConfig; this.configurationService = configurationService; this.name = ControllerConfiguration.ensureValidName(name, associatedReconcilerClassName); @@ -120,6 +125,7 @@ protected ResolvedControllerConfiguration( ControllerConfiguration.ensureValidFinalizerName(finalizer, getResourceTypeName()); this.fieldManager = fieldManager; this.triggerReconcilerOnAllEvents = triggerReconcilerOnAllEvents; + this.withoutDefaultFilters = withoutDefaultFilters; } protected ResolvedControllerConfiguration( @@ -139,6 +145,7 @@ protected ResolvedControllerConfiguration( null, configurationService, InformerConfiguration.builder(resourceClass).buildForController(), + false, false); } @@ -234,4 +241,9 @@ public String fieldManager() { public boolean triggerReconcilerOnAllEvents() { return triggerReconcilerOnAllEvents; } + + @Override + public boolean isWithoutDefaultFilters() { + return withoutDefaultFilters; + } } 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 d305c28824..f3c6a62340 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 @@ -105,4 +105,15 @@ MaxReconciliationInterval maxReconciliationInterval() default * documentation for further details. */ boolean triggerReconcilerOnAllEvents() default false; + + /** + * When set to {@code true}, JOSDK will not apply its default internal update filters (generation- + * aware, finalizer-needed, marked-for-deletion) to the controller's event source. The user's + * {@link Informer#onUpdateFilter()} becomes the sole filter and has full control. To keep any of + * the default behavior, compose it explicitly using the static methods on {@link + * io.javaoperatorsdk.operator.processing.event.source.controller.InternalEventFilters}. + * + * @return whether JOSDK's internal update filters are skipped + */ + boolean withoutDefaultFilters() default false; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSource.java index dfa94577f7..2632886a63 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSource.java @@ -52,18 +52,25 @@ public ControllerEventSource(Controller controller) { this.controller = controller; final var config = controller.getConfiguration(); - OnUpdateFilter internalOnUpdateFilter = - onUpdateFinalizerNeededAndApplied(controller.useFinalizer(), config.getFinalizerName()) - .or(onUpdateGenerationAware(config.isGenerationAware())) - .or(onUpdateMarkedForDeletion()); // by default the on add should be processed in all cases regarding internal filters final var informerConfig = config.getInformerConfig(); Optional.ofNullable(informerConfig.getOnAddFilter()).ifPresent(this::setOnAddFilter); - Optional.ofNullable(informerConfig.getOnUpdateFilter()) - .ifPresentOrElse( - filter -> setOnUpdateFilter(filter.and(internalOnUpdateFilter)), - () -> setOnUpdateFilter(internalOnUpdateFilter)); + + if (config.isWithoutDefaultFilters()) { + var userFilter = informerConfig.getOnUpdateFilter(); + setOnUpdateFilter(userFilter != null ? userFilter : (newResource, oldResource) -> true); + } else { + OnUpdateFilter internalOnUpdateFilter = + onUpdateFinalizerNeededAndApplied(controller.useFinalizer(), config.getFinalizerName()) + .or(onUpdateGenerationAware(config.isGenerationAware())) + .or(onUpdateMarkedForDeletion()); + Optional.ofNullable(informerConfig.getOnUpdateFilter()) + .ifPresentOrElse( + filter -> setOnUpdateFilter(filter.and(internalOnUpdateFilter)), + () -> setOnUpdateFilter(internalOnUpdateFilter)); + } + Optional.ofNullable(informerConfig.getGenericFilter()).ifPresent(this::setGenericFilter); setControllerConfiguration(config); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/InternalEventFilters.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/InternalEventFilters.java index 747f9f860c..6519fab59b 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/InternalEventFilters.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/InternalEventFilters.java @@ -22,7 +22,7 @@ public class InternalEventFilters { private InternalEventFilters() {} - static OnUpdateFilter onUpdateMarkedForDeletion() { + public static OnUpdateFilter onUpdateMarkedForDeletion() { // the old resource is checked since in corner cases users might still want to update the status // for a resource that is marked for deletion @@ -30,7 +30,7 @@ static OnUpdateFilter onUpdateMarkedForDeletion() { !oldResource.isMarkedForDeletion() && newResource.isMarkedForDeletion(); } - static OnUpdateFilter onUpdateGenerationAware( + public static OnUpdateFilter onUpdateGenerationAware( boolean generationAware) { return (newResource, oldResource) -> { @@ -46,7 +46,7 @@ static OnUpdateFilter onUpdateGenerationAware( }; } - static OnUpdateFilter onUpdateFinalizerNeededAndApplied( + public static OnUpdateFilter onUpdateFinalizerNeededAndApplied( boolean useFinalizer, String finalizerName) { return (newResource, oldResource) -> { if (useFinalizer) { diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSourceTest.java index f8cb54f68e..d3fafe8a95 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSourceTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSourceTest.java @@ -140,13 +140,45 @@ void callsBroadcastsOnResourceEvents() { eq(ResourceAction.UPDATED), eq(customResource1), eq(customResource1)); } + @Test + void withoutDefaultFiltersUserFilterIsAppliedDirectly() { + TestCustomResource cr = TestUtils.testCustomResource(); + cr.getMetadata().setFinalizers(List.of(FINALIZER)); + cr.getMetadata().setGeneration(1L); + + // Without default filters, only the user filter runs — no internal generation/finalizer checks. + // User filter accepts unconditionally, so the event passes even with same generation. + OnUpdateFilter userFilter = (newRes, oldRes) -> true; + source = new ControllerEventSource<>(new TestController(null, userFilter, null, true)); + setUpSource(source, true, controllerConfig); + + source.handleEvent(ResourceAction.UPDATED, cr, cr, null); + + verify(eventHandler, times(1)).handleEvent(any()); + } + + @Test + void withoutDefaultFiltersUserFilterCanRejectEvents() { + TestCustomResource cr = TestUtils.testCustomResource(); + + OnUpdateFilter userFilter = (newRes, oldRes) -> false; + source = new ControllerEventSource<>(new TestController(null, userFilter, null, true)); + setUpSource(source, true, controllerConfig); + + source.handleEvent(ResourceAction.UPDATED, cr, cr, null); + + verify(eventHandler, never()).handleEvent(any()); + } + @Test void filtersOutEventsOnAddAndUpdate() { TestCustomResource cr = TestUtils.testCustomResource(); OnAddFilter onAddFilter = (res) -> false; OnUpdateFilter onUpdatePredicate = (res, res2) -> false; - source = new ControllerEventSource<>(new TestController(onAddFilter, onUpdatePredicate, null)); + source = + new ControllerEventSource<>( + new TestController(onAddFilter, onUpdatePredicate, null, false)); setUpSource(source, true, controllerConfig); source.handleEvent(ResourceAction.ADDED, cr, null, null); @@ -159,7 +191,7 @@ void filtersOutEventsOnAddAndUpdate() { void genericFilterFiltersOutAddUpdateAndDeleteEvents() { TestCustomResource cr = TestUtils.testCustomResource(); - source = new ControllerEventSource<>(new TestController(null, null, res -> false)); + source = new ControllerEventSource<>(new TestController(null, null, res -> false, false)); setUpSource(source, true, controllerConfig); source.handleEvent(ResourceAction.ADDED, cr, null, null); @@ -174,7 +206,7 @@ void ownUpdateEchoIsFilteredOutByEventFilter() throws InterruptedException { // End-to-end smoke for the event-filter wiring on the controller path: an event for our // own write must not propagate. Detail-level filter scenarios are covered in // EventingDetailTest / EventFilterSupportTest. - source = spy(new ControllerEventSource<>(new TestController(null, null, null))); + source = spy(new ControllerEventSource<>(new TestController(null, null, null, false))); setUpSource(source, true, controllerConfig); doReturn(Optional.empty()).when(source).get(any()); @@ -189,7 +221,7 @@ void ownUpdateEchoIsFilteredOutByEventFilter() throws InterruptedException { @Test void foreignUpdateDuringFilteringPropagatesAsUpdate() { // An external event during the filter window must surface (not be filtered as own). - source = spy(new ControllerEventSource<>(new TestController(null, null, null))); + source = spy(new ControllerEventSource<>(new TestController(null, null, null, false))); setUpSource(source, true, controllerConfig); var latch = sendForEventFilteringUpdate(2); @@ -203,7 +235,7 @@ void foreignUpdateDuringFilteringPropagatesAsUpdate() { void deleteEventDuringFilteringPropagatesAsDelete() { // A DELETE arriving during the filter window must surface — the resource has gone, // so the filter must not silence it just because our own write is still tracking RVs. - source = spy(new ControllerEventSource<>(new TestController(null, null, null))); + source = spy(new ControllerEventSource<>(new TestController(null, null, null, false))); setUpSource(source, true, controllerConfig); var latch = sendForEventFilteringUpdate(2); @@ -223,7 +255,7 @@ void deleteEventDuringFilteringPropagatesAsDelete() { void multipleForeignEventsDuringFilteringMergeIntoSingleEvent() { // Several external events during one filter window collapse into a single // synthesized event spanning prev → latest seen. - source = spy(new ControllerEventSource<>(new TestController(null, null, null))); + source = spy(new ControllerEventSource<>(new TestController(null, null, null, false))); setUpSource(source, true, controllerConfig); var latch = sendForEventFilteringUpdate(2); @@ -266,17 +298,19 @@ private static class TestController extends Controller { public TestController( OnAddFilter onAddFilter, OnUpdateFilter onUpdateFilter, - GenericFilter genericFilter) { + GenericFilter genericFilter, + boolean withoutDefaultFilters) { super( reconciler, - new TestConfiguration(true, onAddFilter, onUpdateFilter, genericFilter), + new TestConfiguration( + true, onAddFilter, onUpdateFilter, genericFilter, withoutDefaultFilters), MockKubernetesClient.client(TestCustomResource.class)); } public TestController(boolean generationAware) { super( reconciler, - new TestConfiguration(generationAware, null, null, null), + new TestConfiguration(generationAware, null, null, null, false), MockKubernetesClient.client(TestCustomResource.class)); } @@ -298,7 +332,8 @@ public TestConfiguration( boolean generationAware, OnAddFilter onAddFilter, OnUpdateFilter onUpdateFilter, - GenericFilter genericFilter) { + GenericFilter genericFilter, + boolean withoutDefaultFilters) { super( "test", generationAware, @@ -316,7 +351,8 @@ public TestConfiguration( .withGenericFilter(genericFilter) .withComparableResourceVersions(true) .buildForController(), - false); + false, + withoutDefaultFilters); } } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/filter/WithoutDefaultFiltersIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/filter/WithoutDefaultFiltersIT.java new file mode 100644 index 0000000000..d305610f9b --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/filter/WithoutDefaultFiltersIT.java @@ -0,0 +1,86 @@ +/* + * Copyright Java Operator SDK Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.javaoperatorsdk.operator.baseapi.filter; + +import java.time.Duration; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +class WithoutDefaultFiltersIT { + + public static final String RESOURCE_NAME = "without-default-filters-test1"; + public static final int POLL_DELAY = 150; + + @RegisterExtension + LocallyRunOperatorExtension operator = + LocallyRunOperatorExtension.builder() + .withReconciler(new WithoutDefaultFiltersReconciler()) + .build(); + + @Test + void userFilterFullyControlsUpdateEvents() { + var res = operator.create(createResource()); + + await() + .pollDelay(Duration.ofMillis(POLL_DELAY)) + .untilAsserted(() -> assertThat(reconciler().getNumberOfExecutions()).isEqualTo(1)); + + res = operator.get(FilterTestCustomResource.class, RESOURCE_NAME); + res.getSpec().setValue("updated"); + operator.replace(res); + + await() + .pollDelay(Duration.ofMillis(POLL_DELAY)) + .untilAsserted(() -> assertThat(reconciler().getNumberOfExecutions()).isEqualTo(2)); + + res = operator.get(FilterTestCustomResource.class, RESOURCE_NAME); + res.getMetadata() + .setAnnotations(Map.of(WithoutDefaultFiltersReconciler.TRIGGER_ANNOTATION, "true")); + operator.replace(res); + + await() + .pollDelay(Duration.ofMillis(POLL_DELAY)) + .untilAsserted(() -> assertThat(reconciler().getNumberOfExecutions()).isEqualTo(3)); + + res = operator.get(FilterTestCustomResource.class, RESOURCE_NAME); + res.getMetadata().getAnnotations().remove(WithoutDefaultFiltersReconciler.TRIGGER_ANNOTATION); + operator.replace(res); + + await() + .pollDelay(Duration.ofMillis(POLL_DELAY)) + .untilAsserted(() -> assertThat(reconciler().getNumberOfExecutions()).isEqualTo(3)); + } + + private WithoutDefaultFiltersReconciler reconciler() { + return operator.getReconcilerOfType(WithoutDefaultFiltersReconciler.class); + } + + FilterTestCustomResource createResource() { + var resource = new FilterTestCustomResource(); + resource.setMetadata(new ObjectMetaBuilder().withName(RESOURCE_NAME).build()); + resource.setSpec(new FilterTestResourceSpec()); + resource.getSpec().setValue("initial"); + return resource; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/filter/WithoutDefaultFiltersReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/filter/WithoutDefaultFiltersReconciler.java new file mode 100644 index 0000000000..a42f068d04 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/filter/WithoutDefaultFiltersReconciler.java @@ -0,0 +1,45 @@ +/* + * Copyright Java Operator SDK Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.javaoperatorsdk.operator.baseapi.filter; + +import java.util.concurrent.atomic.AtomicInteger; + +import io.javaoperatorsdk.operator.api.config.informer.Informer; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; +import io.javaoperatorsdk.operator.api.reconciler.Reconciler; +import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; + +@ControllerConfiguration( + withoutDefaultFilters = true, + informer = @Informer(onUpdateFilter = WithoutDefaultFiltersUpdateFilter.class)) +public class WithoutDefaultFiltersReconciler implements Reconciler { + + public static final String TRIGGER_ANNOTATION = "trigger-without-default-filters"; + + private final AtomicInteger numberOfExecutions = new AtomicInteger(0); + + @Override + public UpdateControl reconcile( + FilterTestCustomResource resource, Context context) { + numberOfExecutions.incrementAndGet(); + return UpdateControl.noUpdate(); + } + + public int getNumberOfExecutions() { + return numberOfExecutions.get(); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/filter/WithoutDefaultFiltersUpdateFilter.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/filter/WithoutDefaultFiltersUpdateFilter.java new file mode 100644 index 0000000000..8281689f5a --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/filter/WithoutDefaultFiltersUpdateFilter.java @@ -0,0 +1,39 @@ +/* + * Copyright Java Operator SDK Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.javaoperatorsdk.operator.baseapi.filter; + +import io.javaoperatorsdk.operator.processing.event.source.controller.InternalEventFilters; +import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter; + +public class WithoutDefaultFiltersUpdateFilter implements OnUpdateFilter { + + private final OnUpdateFilter composed = + InternalEventFilters.onUpdateGenerationAware(true) + .or( + (newResource, oldResource) -> { + var annotations = newResource.getMetadata().getAnnotations(); + return annotations != null + && "true" + .equals( + annotations.get(WithoutDefaultFiltersReconciler.TRIGGER_ANNOTATION)); + }); + + @Override + public boolean accept( + FilterTestCustomResource newResource, FilterTestCustomResource oldResource) { + return composed.accept(newResource, oldResource); + } +}