|  | 
| 5 | 5 | import java.util.Arrays; | 
| 6 | 6 | import java.util.Collections; | 
| 7 | 7 | import java.util.HashMap; | 
|  | 8 | +import java.util.LinkedHashMap; | 
| 8 | 9 | import java.util.List; | 
| 9 | 10 | import java.util.Map; | 
| 10 | 11 | import java.util.Map.Entry; | 
|  | 
| 25 | 26 | import io.javaoperatorsdk.operator.api.reconciler.Context; | 
| 26 | 27 | import io.javaoperatorsdk.operator.processing.LoggingUtils; | 
| 27 | 28 | 
 | 
|  | 29 | +import com.github.difflib.DiffUtils; | 
|  | 30 | +import com.github.difflib.UnifiedDiffUtils; | 
|  | 31 | + | 
| 28 | 32 | /** | 
| 29 | 33 |  * Matches the actual state on the server vs the desired state. Based on the managedFields of SSA. | 
| 30 | 34 |  * | 
| @@ -103,20 +107,66 @@ public boolean matches(R actual, R desired, Context<?> context) { | 
| 103 | 107 | 
 | 
| 104 | 108 |     removeIrrelevantValues(desiredMap); | 
| 105 | 109 | 
 | 
| 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); | 
| 108 | 118 |     } | 
| 109 | 119 | 
 | 
| 110 |  | -    return prunedActual.equals(desiredMap); | 
|  | 120 | +    return matches; | 
| 111 | 121 |   } | 
| 112 | 122 | 
 | 
| 113 |  | -  private void logDiff(Map<String, Object> prunedActualMap, Map<String, Object> desiredMap, | 
|  | 123 | +  private String getDiff(Map<String, Object> prunedActualMap, Map<String, Object> desiredMap, | 
| 114 | 124 |       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 | +      } | 
| 119 | 168 |     } | 
|  | 169 | +    return sortedList; | 
| 120 | 170 |   } | 
| 121 | 171 | 
 | 
| 122 | 172 |   /** | 
|  | 
0 commit comments