diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java
index 382ac7525c..f8e188999f 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java
@@ -69,12 +69,18 @@ public R create(R desired, P primary, Context
context) {
}
addMetadata(false, null, desired, primary, context);
final var resource = prepare(context, desired, primary, "Creating");
- return useSSA(context)
- ? resource
- .fieldManager(context.getControllerConfiguration().fieldManager())
- .forceConflicts()
- .serverSideApply()
- : resource.create();
+ var result =
+ useSSA(context)
+ ? resource
+ .fieldManager(context.getControllerConfiguration().fieldManager())
+ .forceConflicts()
+ .serverSideApply()
+ : resource.create();
+ if (useSSA(context)) {
+ SSABasedGenericKubernetesResourceMatcher.getInstance()
+ .findDefaultsAndNormalizations(result, desired, context);
+ }
+ return result;
}
public R update(R actual, R desired, P primary, Context
context) {
@@ -92,6 +98,8 @@ public R update(R actual, R desired, P primary, Context
context) {
.fieldManager(context.getControllerConfiguration().fieldManager())
.forceConflicts()
.serverSideApply();
+ SSABasedGenericKubernetesResourceMatcher.getInstance()
+ .findDefaultsAndNormalizations(actual, desired, context);
} else {
var updatedActual = GenericResourceUpdater.updateResource(actual, desired, context);
updatedResource = prepare(context, updatedActual, primary, "Updating").update();
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 eed766fc95..1b0172823e 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
@@ -4,6 +4,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -27,6 +28,7 @@
import io.javaoperatorsdk.operator.OperatorException;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.processing.LoggingUtils;
+import io.javaoperatorsdk.operator.processing.event.ResourceID;
import com.github.difflib.DiffUtils;
import com.github.difflib.UnifiedDiffUtils;
@@ -60,7 +62,13 @@ public class SSABasedGenericKubernetesResourceMatcher {
new SSABasedGenericKubernetesResourceMatcher<>();
private static final List IGNORED_METADATA =
- List.of("creationTimestamp", "deletionTimestamp", "generation", "selfLink", "uid");
+ List.of(
+ "creationTimestamp",
+ "deletionTimestamp",
+ "generation",
+ "selfLink",
+ "uid",
+ "resourceVersion");
@SuppressWarnings("unchecked")
public static SSABasedGenericKubernetesResourceMatcher getInstance() {
@@ -79,8 +87,126 @@ public static SSABasedGenericKubernetesResourceMatcher desired, Map actual) {}
+
+ record CacheKey(ResourceID id, int hash) {}
+
+ private LinkedHashMap defaultsAndNormalizations =
+ new LinkedHashMap();
+
+ public void findDefaultsAndNormalizations(R actual, R desired, Context> context) {
+ SSAState state = getSSAState(actual, desired, context);
+
+ if (state == null) {
+ // exception?
+ }
+
+ // minimize by removing eveything that appears in both
+ minimizeMaps(state.desired, state.actual);
+
+ if (!state.desired.isEmpty() || !state.actual.isEmpty()) {
+ defaultsAndNormalizations.put(
+ new CacheKey(ResourceID.fromResource(actual), desired.hashCode()), state);
+ }
+ }
+
+ void minimizeMaps(Map actual, Map desired) {
+ for (Iterator> iter = desired.entrySet().iterator();
+ iter.hasNext(); ) {
+ var entry = iter.next();
+ var desiredValue = entry.getValue();
+ var actualValue = actual.get(entry.getKey());
+ if (Objects.equals(desiredValue, actualValue)) {
+ iter.remove();
+ actual.remove(entry.getKey());
+ } else if (desiredValue instanceof Map dm) {
+ if (actualValue instanceof Map am) {
+ minimizeMaps(am, dm);
+ if (am.isEmpty()) {
+ actual.remove(entry.getKey());
+ }
+ }
+ if (dm.isEmpty()) {
+ iter.remove();
+ }
+ } else if (desiredValue instanceof List dl) {
+ if (actualValue instanceof List al) {
+ minimizeLists(al, dl);
+ if ((dl.isEmpty() || dl.stream().allMatch(Objects::isNull))
+ && (al.isEmpty() || al.stream().allMatch(Objects::isNull))) {
+ iter.remove();
+ actual.remove(entry.getKey());
+ }
+ }
+ }
+ }
+ }
+
+ void minimizeLists(List