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..93e296924b 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 defaultFilters = annotation == null || annotation.defaultFilters(); + InformerConfiguration

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

ResolvedControllerConfiguration

controllerCon dependentFieldManager, this, informerConfig, - triggerReconcilerOnAllEvents); + triggerReconcilerOnAllEvents, + defaultFilters); } /** 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..d3c4c60082 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 isDefaultFilters() { + return true; + } } 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..555c2ec3d4 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 defaultFilters; 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.defaultFilters = original.isDefaultFilters(); } public ControllerConfigurationOverrider withFinalizer(String finalizer) { @@ -186,6 +188,11 @@ public ControllerConfigurationOverrider withTriggerReconcilerOnAllEvents( return this; } + public ControllerConfigurationOverrider withDefaultFilters(boolean defaultFilters) { + this.defaultFilters = defaultFilters; + 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, + defaultFilters, 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..91cfaafa8f 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 defaultFilters; private WorkflowSpec workflowSpec; public ResolvedControllerConfiguration(ControllerConfiguration

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

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

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

informerConfig, - boolean triggerReconcilerOnAllEvents) { + boolean triggerReconcilerOnAllEvents, + boolean defaultFilters) { 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.defaultFilters = defaultFilters; } protected ResolvedControllerConfiguration( @@ -139,7 +145,8 @@ protected ResolvedControllerConfiguration( null, configurationService, InformerConfiguration.builder(resourceClass).buildForController(), - false); + false, + true); } @Override @@ -234,4 +241,9 @@ public String fieldManager() { public boolean triggerReconcilerOnAllEvents() { return triggerReconcilerOnAllEvents; } + + @Override + public boolean isDefaultFilters() { + return defaultFilters; + } } 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..70ae7435d1 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 false}, 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 applied + */ + boolean defaultFilters() default true; } 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..1f5f638144 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,24 @@ 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.isDefaultFilters()) { + OnUpdateFilter internalOnUpdateFilter = + defaultFilters( + controller.useFinalizer(), config.getFinalizerName(), config.isGenerationAware()); + Optional.ofNullable(informerConfig.getOnUpdateFilter()) + .ifPresentOrElse( + filter -> setOnUpdateFilter(filter.and(internalOnUpdateFilter)), + () -> setOnUpdateFilter(internalOnUpdateFilter)); + } else { + var userFilter = informerConfig.getOnUpdateFilter(); + setOnUpdateFilter(userFilter != null ? userFilter : (newResource, oldResource) -> true); + } + 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..20bea0106a 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) { @@ -61,4 +61,11 @@ static OnUpdateFilter onUpdateFinalizerNeededAndAppli } }; } + + public static OnUpdateFilter defaultFilters( + boolean useFinalizer, String finalizerName, boolean generationAware) { + return InternalEventFilters.onUpdateFinalizerNeededAndApplied(useFinalizer, finalizerName) + .or(onUpdateGenerationAware(generationAware)) + .or(onUpdateMarkedForDeletion()); + } } 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..71ad7314fe 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,44 @@ 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, false)); + 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, false)); + 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, true)); setUpSource(source, true, controllerConfig); source.handleEvent(ResourceAction.ADDED, cr, null, null); @@ -159,7 +190,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, true)); setUpSource(source, true, controllerConfig); source.handleEvent(ResourceAction.ADDED, cr, null, null); @@ -174,7 +205,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, true))); setUpSource(source, true, controllerConfig); doReturn(Optional.empty()).when(source).get(any()); @@ -189,7 +220,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, true))); setUpSource(source, true, controllerConfig); var latch = sendForEventFilteringUpdate(2); @@ -203,7 +234,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, true))); setUpSource(source, true, controllerConfig); var latch = sendForEventFilteringUpdate(2); @@ -223,7 +254,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, true))); setUpSource(source, true, controllerConfig); var latch = sendForEventFilteringUpdate(2); @@ -266,17 +297,18 @@ private static class TestController extends Controller { public TestController( OnAddFilter onAddFilter, OnUpdateFilter onUpdateFilter, - GenericFilter genericFilter) { + GenericFilter genericFilter, + boolean defaultFilters) { super( reconciler, - new TestConfiguration(true, onAddFilter, onUpdateFilter, genericFilter), + new TestConfiguration(true, onAddFilter, onUpdateFilter, genericFilter, defaultFilters), MockKubernetesClient.client(TestCustomResource.class)); } public TestController(boolean generationAware) { super( reconciler, - new TestConfiguration(generationAware, null, null, null), + new TestConfiguration(generationAware, null, null, null, true), MockKubernetesClient.client(TestCustomResource.class)); } @@ -298,7 +330,8 @@ public TestConfiguration( boolean generationAware, OnAddFilter onAddFilter, OnUpdateFilter onUpdateFilter, - GenericFilter genericFilter) { + GenericFilter genericFilter, + boolean defaultFilters) { super( "test", generationAware, @@ -316,7 +349,8 @@ public TestConfiguration( .withGenericFilter(genericFilter) .withComparableResourceVersions(true) .buildForController(), - false); + false, + defaultFilters); } } } 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..a87e9feaa6 --- /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( + defaultFilters = false, + 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); + } +}