Skip to content

Commit 43b8591

Browse files
authored
fix: ignore server managed fields for SSA matching (#2309)
Signed-off-by: Steven Hawkins <[email protected]> Signed-off-by: Attila Mészáros <[email protected]>
1 parent 0b17878 commit 43b8591

File tree

3 files changed

+28
-23
lines changed

3 files changed

+28
-23
lines changed

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

+26-22
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ public class SSABasedGenericKubernetesResourceMatcher<R extends HasMetadata> {
4141
public static final String APPLY_OPERATION = "Apply";
4242
public static final String DOT_KEY = ".";
4343

44+
private static final List<String> IGNORED_METADATA =
45+
Arrays.asList("creationTimestamp", "deletionTimestamp",
46+
"generation", "selfLink", "uid");
47+
4448
@SuppressWarnings("unchecked")
4549
public static <L extends HasMetadata> SSABasedGenericKubernetesResourceMatcher<L> getInstance() {
4650
return INSTANCE;
@@ -58,11 +62,10 @@ public static <L extends HasMetadata> SSABasedGenericKubernetesResourceMatcher<L
5862
private static final Logger log =
5963
LoggerFactory.getLogger(SSABasedGenericKubernetesResourceMatcher.class);
6064

61-
6265
@SuppressWarnings("unchecked")
6366
public boolean matches(R actual, R desired, Context<?> context) {
64-
var optionalManagedFieldsEntry =
65-
checkIfFieldManagerExists(actual, context.getControllerConfiguration().fieldManager());
67+
var optionalManagedFieldsEntry = checkIfFieldManagerExists(actual,
68+
context.getControllerConfiguration().fieldManager());
6669
// If no field is managed by our controller, that means the controller hasn't touched the
6770
// resource yet and the resource probably doesn't match the desired state. Not matching here
6871
// means that the resource will need to be updated and since this will be done using SSA, the
@@ -86,7 +89,8 @@ public boolean matches(R actual, R desired, Context<?> context) {
8689

8790
var prunedActual = new HashMap<String, Object>(actualMap.size());
8891
keepOnlyManagedFields(prunedActual, actualMap,
89-
managedFieldsEntry.getFieldsV1().getAdditionalProperties(), objectMapper);
92+
managedFieldsEntry.getFieldsV1().getAdditionalProperties(),
93+
objectMapper);
9094

9195
removeIrrelevantValues(desiredMap);
9296

@@ -110,9 +114,8 @@ private void sanitizeState(R actual, R desired, Map<String, Object> actualMap) {
110114
for (int i = 0; i < claims; i++) {
111115
if (desiredStatefulSet.getSpec().getVolumeClaimTemplates().get(i).getSpec()
112116
.getVolumeMode() == null) {
113-
Optional
114-
.ofNullable(GenericKubernetesResource.get(actualMap, "spec", "volumeClaimTemplates",
115-
i, "spec"))
117+
Optional.ofNullable(
118+
GenericKubernetesResource.get(actualMap, "spec", "volumeClaimTemplates", i, "spec"))
116119
.map(Map.class::cast).ifPresent(m -> m.remove("volumeMode"));
117120
}
118121
if (desiredStatefulSet.getSpec().getVolumeClaimTemplates().get(i).getStatus() == null) {
@@ -131,6 +134,7 @@ private static void removeIrrelevantValues(Map<String, Object> desiredMap) {
131134
var metadata = (Map<String, Object>) desiredMap.get(METADATA_KEY);
132135
metadata.remove(NAME_KEY);
133136
metadata.remove(NAMESPACE_KEY);
137+
IGNORED_METADATA.forEach(metadata::remove);
134138
if (metadata.isEmpty()) {
135139
desiredMap.remove(METADATA_KEY);
136140
}
@@ -163,7 +167,8 @@ private static void keepOnlyManagedFields(Map<String, Object> result,
163167
} else {
164168
// basically if we should traverse further
165169
fillResultsAndTraverseFurther(result, actualMap, managedFields, objectMapper, key,
166-
keyInActual, managedFieldValue);
170+
keyInActual,
171+
managedFieldValue);
167172
}
168173
} else {
169174
// this should handle the case when the value is complex in the actual map (not just a
@@ -181,8 +186,9 @@ private static void keepOnlyManagedFields(Map<String, Object> result,
181186

182187
@SuppressWarnings("unchecked")
183188
private static void fillResultsAndTraverseFurther(Map<String, Object> result,
184-
Map<String, Object> actualMap, Map<String, Object> managedFields,
185-
KubernetesSerialization objectMapper, String key, String keyInActual,
189+
Map<String, Object> actualMap,
190+
Map<String, Object> managedFields, KubernetesSerialization objectMapper, String key,
191+
String keyInActual,
186192
Object managedFieldValue) {
187193
var emptyMapValue = new HashMap<String, Object>();
188194
result.put(keyInActual, emptyMapValue);
@@ -223,8 +229,9 @@ private static void handleListKeyEntrySet(Map<String, Object> result,
223229
if (DOT_KEY.equals(listEntry.getKey())) {
224230
continue;
225231
}
226-
var actualListEntry = selectListEntryBasedOnKey(keyWithoutPrefix(listEntry.getKey()),
227-
actualValueList, objectMapper);
232+
var actualListEntry =
233+
selectListEntryBasedOnKey(keyWithoutPrefix(listEntry.getKey()), actualValueList,
234+
objectMapper);
228235
targetValuesByIndex.put(actualListEntry.getKey(), actualListEntry.getValue());
229236
managedEntryByIndex.put(actualListEntry.getKey(), (Map<String, Object>) listEntry.getValue());
230237
}
@@ -301,8 +308,7 @@ private static boolean isKeyPrefixedSkippingDotKey(Set<Map.Entry<String, Object>
301308
@SuppressWarnings("unchecked")
302309
private static java.util.Map.Entry<Integer, Map<String, Object>> selectListEntryBasedOnKey(
303310
String key,
304-
List<Map<String, Object>> values,
305-
KubernetesSerialization objectMapper) {
311+
List<Map<String, Object>> values, KubernetesSerialization objectMapper) {
306312
Map<String, Object> ids = objectMapper.unmarshal(key, Map.class);
307313
List<Map<String, Object>> possibleTargets = new ArrayList<>(1);
308314
int index = -1;
@@ -314,9 +320,8 @@ private static java.util.Map.Entry<Integer, Map<String, Object>> selectListEntry
314320
}
315321
}
316322
if (possibleTargets.isEmpty()) {
317-
throw new IllegalStateException(
318-
"Cannot find list element for key:" + key + ", in map: "
319-
+ values.stream().map(Map::keySet).collect(Collectors.toList()));
323+
throw new IllegalStateException("Cannot find list element for key:" + key + ", in map: "
324+
+ values.stream().map(Map::keySet).collect(Collectors.toList()));
320325
}
321326
if (possibleTargets.size() > 1) {
322327
throw new IllegalStateException(
@@ -327,7 +332,6 @@ private static java.util.Map.Entry<Integer, Map<String, Object>> selectListEntry
327332
return new AbstractMap.SimpleEntry<>(finalIndex, possibleTargets.get(0));
328333
}
329334

330-
331335
private Optional<ManagedFieldsEntry> checkIfFieldManagerExists(R actual, String fieldManager) {
332336
var targetManagedFields = actual.getMetadata().getManagedFields().stream()
333337
// Only the apply operations are interesting for us since those were created properly be SSA
@@ -338,14 +342,14 @@ private Optional<ManagedFieldsEntry> checkIfFieldManagerExists(R actual, String
338342
.collect(Collectors.toList());
339343
if (targetManagedFields.isEmpty()) {
340344
log.debug("No field manager exists for resource {} with name: {} and operation Apply ",
341-
actual.getKind(), actual.getMetadata().getName());
345+
actual.getKind(),
346+
actual.getMetadata().getName());
342347
return Optional.empty();
343348
}
344349
// this should not happen in theory
345350
if (targetManagedFields.size() > 1) {
346-
throw new OperatorException(
347-
"More than one field manager exists with name: " + fieldManager + "in resource: " +
348-
actual.getKind() + " with name: " + actual.getMetadata().getName());
351+
throw new OperatorException("More than one field manager exists with name: " + fieldManager
352+
+ "in resource: " + actual.getKind() + " with name: " + actual.getMetadata().getName());
349353
}
350354
return Optional.of(targetManagedFields.get(0));
351355
}

operator-framework-core/src/test/resources/io/javaoperatorsdk/operator/processing/dependent/kubernetes/deployment-with-managed-fields-additional-controller.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ metadata:
44
annotations:
55
deployment.kubernetes.io/revision: "1"
66
creationTimestamp: "2023-06-01T08:43:47Z"
7-
generation: 1
7+
generation: 2
88
managedFields:
99
- apiVersion: apps/v1
1010
fieldsType: FieldsV1

operator-framework-core/src/test/resources/io/javaoperatorsdk/operator/processing/dependent/kubernetes/nginx-deployment.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
22
kind: Deployment
33
metadata:
44
name: "test"
5+
generation: 1
56
spec:
67
progressDeadlineSeconds: 600
78
revisionHistoryLimit: 10

0 commit comments

Comments
 (0)