diff --git a/operator-framework-core/pom.xml b/operator-framework-core/pom.xml index f0b30f33a3..0ead5bab7f 100644 --- a/operator-framework-core/pom.xml +++ b/operator-framework-core/pom.xml @@ -14,6 +14,10 @@ Core framework for implementing Kubernetes operators + + io.github.java-diff-utils + java-diff-utils + io.fabric8 kubernetes-client 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 bd4a6aa1d3..c65528eb12 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 @@ -5,6 +5,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -25,6 +26,9 @@ import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.processing.LoggingUtils; +import com.github.difflib.DiffUtils; +import com.github.difflib.UnifiedDiffUtils; + /** * Matches the actual state on the server vs the desired state. Based on the managedFields of SSA. * @@ -103,20 +107,66 @@ public boolean matches(R actual, R desired, Context context) { removeIrrelevantValues(desiredMap); - if (LoggingUtils.isNotSensitiveResource(desired)) { - logDiff(prunedActual, desiredMap, objectMapper); + var matches = prunedActual.equals(desiredMap); + + if (!matches && log.isDebugEnabled() && LoggingUtils.isNotSensitiveResource(desired)) { + var diff = getDiff(prunedActual, desiredMap, objectMapper); + log.debug( + "Diff between actual and desired state for resource: {} with name: {} in namespace: {} is: \n{}", + actual.getKind(), actual.getMetadata().getName(), actual.getMetadata().getNamespace(), + diff); } - return prunedActual.equals(desiredMap); + return matches; } - private void logDiff(Map prunedActualMap, Map desiredMap, + private String getDiff(Map prunedActualMap, Map desiredMap, KubernetesSerialization serialization) { - if (log.isDebugEnabled()) { - var actualYaml = serialization.asYaml(prunedActualMap); - var desiredYaml = serialization.asYaml(desiredMap); - log.debug("Pruned actual yaml: \n {} \n desired yaml: \n {} ", actualYaml, desiredYaml); + var actualYaml = serialization.asYaml(sortMap(prunedActualMap)); + var desiredYaml = serialization.asYaml(sortMap(desiredMap)); + if (log.isTraceEnabled()) { + log.trace("Pruned actual resource: \n {} \ndesired resource: \n {} ", actualYaml, + desiredYaml); + } + + var patch = DiffUtils.diff(actualYaml.lines().toList(), desiredYaml.lines().toList()); + List unifiedDiff = + UnifiedDiffUtils.generateUnifiedDiff("", "", actualYaml.lines().toList(), patch, 1); + return String.join("\n", unifiedDiff); + } + + @SuppressWarnings("unchecked") + Map sortMap(Map map) { + List sortedKeys = new ArrayList<>(map.keySet()); + Collections.sort(sortedKeys); + + Map sortedMap = new LinkedHashMap<>(); + for (String key : sortedKeys) { + Object value = map.get(key); + if (value instanceof Map) { + sortedMap.put(key, sortMap((Map) value)); + } else if (value instanceof List) { + sortedMap.put(key, sortListItems((List) value)); + } else { + sortedMap.put(key, value); + } + } + return sortedMap; + } + + @SuppressWarnings("unchecked") + List sortListItems(List list) { + List sortedList = new ArrayList<>(); + for (Object item : list) { + if (item instanceof Map) { + sortedList.add(sortMap((Map) item)); + } else if (item instanceof List) { + sortedList.add(sortListItems((List) item)); + } else { + sortedList.add(item); + } } + return sortedList; } /** 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 cf0f67887b..9855527863 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 @@ -1,5 +1,8 @@ package io.javaoperatorsdk.operator.processing.dependent.kubernetes; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; import java.util.Map; import org.junit.jupiter.api.BeforeEach; @@ -121,4 +124,47 @@ private R loadResource(String fileName, Class clazz) { fileName); } + @Test + @SuppressWarnings("unchecked") + void sortListItemsTest() { + Map nestedMap1 = new HashMap<>(); + nestedMap1.put("z", 26); + nestedMap1.put("y", 25); + + Map nestedMap2 = new HashMap<>(); + nestedMap2.put("b", 26); + nestedMap2.put("c", 25); + nestedMap2.put("a", 24); + + List unsortedListItems = Arrays.asList(1, nestedMap1, nestedMap2); + List sortedListItems = matcher.sortListItems(unsortedListItems); + + assertThat(sortedListItems).element(0).isEqualTo(1); + + Map sortedNestedMap1 = (Map) sortedListItems.get(1); + assertThat(sortedNestedMap1.keySet()).containsExactly("y", "z"); + + Map sortedNestedMap2 = (Map) sortedListItems.get(2); + assertThat(sortedNestedMap2.keySet()).containsExactly("a", "b", "c"); + } + + @Test + @SuppressWarnings("unchecked") + void testSortMapWithNestedMap() { + Map nestedMap = new HashMap<>(); + nestedMap.put("z", 26); + nestedMap.put("y", 25); + + Map unsortedMap = new HashMap<>(); + unsortedMap.put("b", nestedMap); + unsortedMap.put("a", 1); + unsortedMap.put("c", 2); + + Map sortedMap = matcher.sortMap(unsortedMap); + + assertThat(sortedMap.keySet()).containsExactly("a", "b", "c"); + + Map sortedNestedMap = (Map) sortedMap.get("b"); + assertThat(sortedNestedMap.keySet()).containsExactly("y", "z"); + } } diff --git a/pom.xml b/pom.xml index d36c1e440a..a89059e155 100644 --- a/pom.xml +++ b/pom.xml @@ -75,6 +75,7 @@ 3.1.8 0.9.11 2.16.1 + 4.12 2.11 3.12.1 @@ -161,6 +162,11 @@ ${mokito.version} + + io.github.java-diff-utils + java-diff-utils + ${java.diff.version} + org.slf4j slf4j-api