|  | 
| 4 | 4 | import java.util.ArrayList; | 
| 5 | 5 | import java.util.Collections; | 
| 6 | 6 | import java.util.HashMap; | 
|  | 7 | +import java.util.LinkedHashMap; | 
| 7 | 8 | import java.util.List; | 
| 8 | 9 | import java.util.Map; | 
| 9 | 10 | import java.util.Map.Entry; | 
|  | 
| 33 | 34 | 
 | 
| 34 | 35 | import static io.javaoperatorsdk.operator.processing.dependent.kubernetes.ResourceRequirementsSanitizer.sanitizeResourceRequirements; | 
| 35 | 36 | 
 | 
|  | 37 | +import com.github.difflib.DiffUtils; | 
|  | 38 | +import com.github.difflib.UnifiedDiffUtils; | 
|  | 39 | + | 
| 36 | 40 | /** | 
| 37 | 41 |  * Matches the actual state on the server vs the desired state. Based on the managedFields of SSA. | 
| 38 | 42 |  * <p> | 
| @@ -107,20 +111,66 @@ public boolean matches(R actual, R desired, Context<?> context) { | 
| 107 | 111 | 
 | 
| 108 | 112 |     removeIrrelevantValues(desiredMap); | 
| 109 | 113 | 
 | 
| 110 |  | -    if (LoggingUtils.isNotSensitiveResource(desired)) { | 
| 111 |  | -      logDiff(prunedActual, desiredMap, objectMapper); | 
|  | 114 | +    var matches = prunedActual.equals(desiredMap); | 
|  | 115 | + | 
|  | 116 | +    if (!matches && log.isDebugEnabled() && LoggingUtils.isNotSensitiveResource(desired)) { | 
|  | 117 | +      var diff = getDiff(prunedActual, desiredMap, objectMapper); | 
|  | 118 | +      log.debug( | 
|  | 119 | +          "Diff between actual and desired state for resource: {} with name: {} in namespace: {} is: \n{}", | 
|  | 120 | +          actual.getKind(), actual.getMetadata().getName(), actual.getMetadata().getNamespace(), | 
|  | 121 | +          diff); | 
| 112 | 122 |     } | 
| 113 | 123 | 
 | 
| 114 |  | -    return prunedActual.equals(desiredMap); | 
|  | 124 | +    return matches; | 
| 115 | 125 |   } | 
| 116 | 126 | 
 | 
| 117 |  | -  private void logDiff(Map<String, Object> prunedActualMap, Map<String, Object> desiredMap, | 
|  | 127 | +  private String getDiff(Map<String, Object> prunedActualMap, Map<String, Object> desiredMap, | 
| 118 | 128 |       KubernetesSerialization serialization) { | 
| 119 |  | -    if (log.isDebugEnabled()) { | 
| 120 |  | -      var actualYaml = serialization.asYaml(prunedActualMap); | 
| 121 |  | -      var desiredYaml = serialization.asYaml(desiredMap); | 
| 122 |  | -      log.debug("Pruned actual yaml: \n {} \n desired yaml: \n {} ", actualYaml, desiredYaml); | 
|  | 129 | +    var actualYaml = serialization.asYaml(sortMap(prunedActualMap)); | 
|  | 130 | +    var desiredYaml = serialization.asYaml(sortMap(desiredMap)); | 
|  | 131 | +    if (log.isTraceEnabled()) { | 
|  | 132 | +      log.trace("Pruned actual resource: \n {} \ndesired resource: \n {} ", actualYaml, | 
|  | 133 | +          desiredYaml); | 
|  | 134 | +    } | 
|  | 135 | + | 
|  | 136 | +    var patch = DiffUtils.diff(actualYaml.lines().toList(), desiredYaml.lines().toList()); | 
|  | 137 | +    List<String> unifiedDiff = | 
|  | 138 | +        UnifiedDiffUtils.generateUnifiedDiff("", "", actualYaml.lines().toList(), patch, 1); | 
|  | 139 | +    return String.join("\n", unifiedDiff); | 
|  | 140 | +  } | 
|  | 141 | + | 
|  | 142 | +  @SuppressWarnings("unchecked") | 
|  | 143 | +  Map<String, Object> sortMap(Map<String, Object> map) { | 
|  | 144 | +    List<String> sortedKeys = new ArrayList<>(map.keySet()); | 
|  | 145 | +    Collections.sort(sortedKeys); | 
|  | 146 | + | 
|  | 147 | +    Map<String, Object> sortedMap = new LinkedHashMap<>(); | 
|  | 148 | +    for (String key : sortedKeys) { | 
|  | 149 | +      Object value = map.get(key); | 
|  | 150 | +      if (value instanceof Map) { | 
|  | 151 | +        sortedMap.put(key, sortMap((Map<String, Object>) value)); | 
|  | 152 | +      } else if (value instanceof List) { | 
|  | 153 | +        sortedMap.put(key, sortListItems((List<Object>) value)); | 
|  | 154 | +      } else { | 
|  | 155 | +        sortedMap.put(key, value); | 
|  | 156 | +      } | 
|  | 157 | +    } | 
|  | 158 | +    return sortedMap; | 
|  | 159 | +  } | 
|  | 160 | + | 
|  | 161 | +  @SuppressWarnings("unchecked") | 
|  | 162 | +  List<Object> sortListItems(List<Object> list) { | 
|  | 163 | +    List<Object> sortedList = new ArrayList<>(); | 
|  | 164 | +    for (Object item : list) { | 
|  | 165 | +      if (item instanceof Map) { | 
|  | 166 | +        sortedList.add(sortMap((Map<String, Object>) item)); | 
|  | 167 | +      } else if (item instanceof List) { | 
|  | 168 | +        sortedList.add(sortListItems((List<Object>) item)); | 
|  | 169 | +      } else { | 
|  | 170 | +        sortedList.add(item); | 
|  | 171 | +      } | 
| 123 | 172 |     } | 
|  | 173 | +    return sortedList; | 
| 124 | 174 |   } | 
| 125 | 175 | 
 | 
| 126 | 176 |   /** | 
|  | 
0 commit comments