Skip to content

Commit 68832a0

Browse files
bachmanity1metacosm
authored andcommitted
feat: improve diff logging (#2544)
* imporve diff logging Signed-off-by: bachmanity1 <[email protected]> * compute diff only when actual doesn't match desired Signed-off-by: bachmanity1 <[email protected]> * slight improvements Signed-off-by: bachmanity1 <[email protected]> * increase context size Signed-off-by: bachmanity1 <[email protected]> * fix style Signed-off-by: bachmanity1 <[email protected]> * calculate diff only if debug is enabled Signed-off-by: bachmanity1 <[email protected]> * print actual resources when trace is enabled Signed-off-by: bachmanity1 <[email protected]> * use java-diff-utils Signed-off-by: bachmanity1 <[email protected]> * add unit tests --------- Signed-off-by: bachmanity1 <[email protected]>
1 parent 0be0f42 commit 68832a0

File tree

4 files changed

+114
-8
lines changed

4 files changed

+114
-8
lines changed

Diff for: operator-framework-core/pom.xml

+4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
<description>Core framework for implementing Kubernetes operators</description>
1515

1616
<dependencies>
17+
<dependency>
18+
<groupId>io.github.java-diff-utils</groupId>
19+
<artifactId>java-diff-utils</artifactId>
20+
</dependency>
1721
<dependency>
1822
<groupId>io.fabric8</groupId>
1923
<artifactId>kubernetes-client</artifactId>

Diff for: operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java

+58-8
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.util.Arrays;
66
import java.util.Collections;
77
import java.util.HashMap;
8+
import java.util.LinkedHashMap;
89
import java.util.List;
910
import java.util.Map;
1011
import java.util.Map.Entry;
@@ -25,6 +26,9 @@
2526
import io.javaoperatorsdk.operator.api.reconciler.Context;
2627
import io.javaoperatorsdk.operator.processing.LoggingUtils;
2728

29+
import com.github.difflib.DiffUtils;
30+
import com.github.difflib.UnifiedDiffUtils;
31+
2832
/**
2933
* Matches the actual state on the server vs the desired state. Based on the managedFields of SSA.
3034
*
@@ -103,20 +107,66 @@ public boolean matches(R actual, R desired, Context<?> context) {
103107

104108
removeIrrelevantValues(desiredMap);
105109

106-
if (LoggingUtils.isNotSensitiveResource(desired)) {
107-
logDiff(prunedActual, desiredMap, objectMapper);
110+
var matches = prunedActual.equals(desiredMap);
111+
112+
if (!matches && log.isDebugEnabled() && LoggingUtils.isNotSensitiveResource(desired)) {
113+
var diff = getDiff(prunedActual, desiredMap, objectMapper);
114+
log.debug(
115+
"Diff between actual and desired state for resource: {} with name: {} in namespace: {} is: \n{}",
116+
actual.getKind(), actual.getMetadata().getName(), actual.getMetadata().getNamespace(),
117+
diff);
108118
}
109119

110-
return prunedActual.equals(desiredMap);
120+
return matches;
111121
}
112122

113-
private void logDiff(Map<String, Object> prunedActualMap, Map<String, Object> desiredMap,
123+
private String getDiff(Map<String, Object> prunedActualMap, Map<String, Object> desiredMap,
114124
KubernetesSerialization serialization) {
115-
if (log.isDebugEnabled()) {
116-
var actualYaml = serialization.asYaml(prunedActualMap);
117-
var desiredYaml = serialization.asYaml(desiredMap);
118-
log.debug("Pruned actual yaml: \n {} \n desired yaml: \n {} ", actualYaml, desiredYaml);
125+
var actualYaml = serialization.asYaml(sortMap(prunedActualMap));
126+
var desiredYaml = serialization.asYaml(sortMap(desiredMap));
127+
if (log.isTraceEnabled()) {
128+
log.trace("Pruned actual resource: \n {} \ndesired resource: \n {} ", actualYaml,
129+
desiredYaml);
130+
}
131+
132+
var patch = DiffUtils.diff(actualYaml.lines().toList(), desiredYaml.lines().toList());
133+
List<String> unifiedDiff =
134+
UnifiedDiffUtils.generateUnifiedDiff("", "", actualYaml.lines().toList(), patch, 1);
135+
return String.join("\n", unifiedDiff);
136+
}
137+
138+
@SuppressWarnings("unchecked")
139+
Map<String, Object> sortMap(Map<String, Object> map) {
140+
List<String> sortedKeys = new ArrayList<>(map.keySet());
141+
Collections.sort(sortedKeys);
142+
143+
Map<String, Object> sortedMap = new LinkedHashMap<>();
144+
for (String key : sortedKeys) {
145+
Object value = map.get(key);
146+
if (value instanceof Map) {
147+
sortedMap.put(key, sortMap((Map<String, Object>) value));
148+
} else if (value instanceof List) {
149+
sortedMap.put(key, sortListItems((List<Object>) value));
150+
} else {
151+
sortedMap.put(key, value);
152+
}
153+
}
154+
return sortedMap;
155+
}
156+
157+
@SuppressWarnings("unchecked")
158+
List<Object> sortListItems(List<Object> list) {
159+
List<Object> sortedList = new ArrayList<>();
160+
for (Object item : list) {
161+
if (item instanceof Map) {
162+
sortedList.add(sortMap((Map<String, Object>) item));
163+
} else if (item instanceof List) {
164+
sortedList.add(sortListItems((List<Object>) item));
165+
} else {
166+
sortedList.add(item);
167+
}
119168
}
169+
return sortedList;
120170
}
121171

122172
/**

Diff for: operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcherTest.java

+46
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package io.javaoperatorsdk.operator.processing.dependent.kubernetes;
22

3+
import java.util.Arrays;
4+
import java.util.HashMap;
5+
import java.util.List;
36
import java.util.Map;
47

58
import org.junit.jupiter.api.BeforeEach;
@@ -121,4 +124,47 @@ private <R> R loadResource(String fileName, Class<R> clazz) {
121124
fileName);
122125
}
123126

127+
@Test
128+
@SuppressWarnings("unchecked")
129+
void sortListItemsTest() {
130+
Map<String, Object> nestedMap1 = new HashMap<>();
131+
nestedMap1.put("z", 26);
132+
nestedMap1.put("y", 25);
133+
134+
Map<String, Object> nestedMap2 = new HashMap<>();
135+
nestedMap2.put("b", 26);
136+
nestedMap2.put("c", 25);
137+
nestedMap2.put("a", 24);
138+
139+
List<Object> unsortedListItems = Arrays.asList(1, nestedMap1, nestedMap2);
140+
List<Object> sortedListItems = matcher.sortListItems(unsortedListItems);
141+
142+
assertThat(sortedListItems).element(0).isEqualTo(1);
143+
144+
Map<String, Object> sortedNestedMap1 = (Map<String, Object>) sortedListItems.get(1);
145+
assertThat(sortedNestedMap1.keySet()).containsExactly("y", "z");
146+
147+
Map<String, Object> sortedNestedMap2 = (Map<String, Object>) sortedListItems.get(2);
148+
assertThat(sortedNestedMap2.keySet()).containsExactly("a", "b", "c");
149+
}
150+
151+
@Test
152+
@SuppressWarnings("unchecked")
153+
void testSortMapWithNestedMap() {
154+
Map<String, Object> nestedMap = new HashMap<>();
155+
nestedMap.put("z", 26);
156+
nestedMap.put("y", 25);
157+
158+
Map<String, Object> unsortedMap = new HashMap<>();
159+
unsortedMap.put("b", nestedMap);
160+
unsortedMap.put("a", 1);
161+
unsortedMap.put("c", 2);
162+
163+
Map<String, Object> sortedMap = matcher.sortMap(unsortedMap);
164+
165+
assertThat(sortedMap.keySet()).containsExactly("a", "b", "c");
166+
167+
Map<String, Object> sortedNestedMap = (Map<String, Object>) sortedMap.get("b");
168+
assertThat(sortedNestedMap.keySet()).containsExactly("y", "z");
169+
}
124170
}

Diff for: pom.xml

+6
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
<caffeine.version>3.1.8</caffeine.version>
7676
<mustache.version>0.9.11</mustache.version>
7777
<commons.io.version>2.16.1</commons.io.version>
78+
<java.diff.version>4.12</java.diff.version>
7879

7980
<fmt-maven-plugin.version>2.11</fmt-maven-plugin.version>
8081
<maven-compiler-plugin.version>3.12.1</maven-compiler-plugin.version>
@@ -161,6 +162,11 @@
161162
<version>${mokito.version}</version>
162163
</dependency>
163164

165+
<dependency>
166+
<groupId>io.github.java-diff-utils</groupId>
167+
<artifactId>java-diff-utils</artifactId>
168+
<version>${java.diff.version}</version>
169+
</dependency>
164170
<dependency>
165171
<groupId>org.slf4j</groupId>
166172
<artifactId>slf4j-api</artifactId>

0 commit comments

Comments
 (0)