diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java index 1868c2872d..eed766fc95 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java @@ -110,7 +110,7 @@ public boolean matches(R actual, R desired, Context context) { removeIrrelevantValues(desiredMap); - var matches = prunedActual.equals(desiredMap); + var matches = matches(prunedActual, desiredMap, actual, desired, context); if (!matches && log.isDebugEnabled() && LoggingUtils.isNotSensitiveResource(desired)) { var diff = getDiff(prunedActual, desiredMap, objectMapper); log.debug( @@ -125,6 +125,28 @@ public boolean matches(R actual, R desired, Context context) { return matches; } + /** + * Compares the desired and actual resources for equality. + * + *

This method can be overridden to implement custom matching logic. The {@code actualMap} is a + * cleaned-up version of the actual resource with managed fields and irrelevant values removed. + * + * @param actualMap the actual resource represented as a map + * @param desiredMap the desired resource represented as a map + * @param actual the actual resource object + * @param desired the desired resource object + * @param context the current matching context + * @return {@code true} if the resources are equal, otherwise {@code false} + */ + protected boolean matches( + Map actualMap, + Map desiredMap, + R actual, + R desired, + Context context) { + return actualMap.equals(desiredMap); + } + private String getDiff( Map prunedActualMap, Map desiredMap, diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcherTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcherTest.java index 69bdf59aff..176531344c 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcherTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcherTest.java @@ -236,6 +236,37 @@ void testSanitizeState_daemonSetWithResources_withMismatch() { assertThat(matcher.matches(actualDaemonSet, desiredDaemonSet, mockedContext)).isFalse(); } + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void testCustomMatcher_returnsExpectedMatchBasedOnReadOnlyLabel(boolean readOnly) { + var desiredConfigMap = + loadResource("configmap.empty-owner-reference-desired.yaml", ConfigMap.class); + desiredConfigMap.getData().put("key1", "another value"); + var actualConfigMap = loadResource("configmap.empty-owner-reference.yaml", ConfigMap.class); + actualConfigMap.getMetadata().getLabels().put("readonly", Boolean.toString(readOnly)); + + var matcher = new ReadOnlyAwareMatcher(); + assertThat(matcher.matches(actualConfigMap, desiredConfigMap, mockedContext)) + .isEqualTo(readOnly); + } + + private static class ReadOnlyAwareMatcher + extends SSABasedGenericKubernetesResourceMatcher { + @Override + protected boolean matches( + Map actualMap, + Map desiredMap, + T actual, + T desired, + Context context) { + var readonly = actual.getMetadata().getLabels().get("readonly"); + if (readonly != null && readonly.equals("true")) { + return true; + } + return actualMap.equals(desiredMap); + } + } + private static R loadResource(String fileName, Class clazz) { return ReconcilerUtils.loadYaml( clazz, SSABasedGenericKubernetesResourceMatcherTest.class, fileName);