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);