Skip to content

Commit 69d0048

Browse files
committed
draft of a generalization of the update matcher
1 parent 4110c8b commit 69d0048

11 files changed

+67
-255
lines changed

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

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.util.List;
77
import java.util.Objects;
88
import java.util.Optional;
9+
import java.util.Set;
910

1011
import io.fabric8.kubernetes.api.model.ConfigMap;
1112
import io.fabric8.kubernetes.api.model.HasMetadata;
@@ -24,6 +25,8 @@ public class GenericKubernetesResourceMatcher<R extends HasMetadata, P extends H
2425
private static final String OP = "op";
2526
public static final String METADATA_LABELS = "/metadata/labels";
2627
public static final String METADATA_ANNOTATIONS = "/metadata/annotations";
28+
// without knowing the CRD we cannot ignore status as it may not be a subresource, so if it's included we expect it to match
29+
private static Set<String> NOT_OTHER_FIELDS = Set.of(SPEC, "/metadata", "/apiVersion", "/kind");
2730

2831
private static final String PATH = "path";
2932
private static final String[] EMPTY_ARRAY = {};
@@ -192,11 +195,11 @@ public static <R extends HasMetadata, P extends HasMetadata> Result<R> match(R d
192195
}
193196
}
194197

195-
final var matched = matchSpec(actualResource, desired, specEquality, context, ignoredPaths);
198+
final var matched = match(actualResource, desired, specEquality, context, ignoredPaths);
196199
return Result.computed(matched, desired);
197200
}
198201

199-
private static <R extends HasMetadata> boolean matchSpec(R actual, R desired, boolean equality,
202+
private static <R extends HasMetadata> boolean match(R actual, R desired, boolean equality,
200203
Context<?> context,
201204
String[] ignoredPaths) {
202205

@@ -208,25 +211,37 @@ private static <R extends HasMetadata> boolean matchSpec(R actual, R desired, bo
208211
final List<String> ignoreList =
209212
ignoredPaths != null && ignoredPaths.length > 0 ? Arrays.asList(ignoredPaths)
210213
: Collections.emptyList();
211-
// reflection will be replaced by this:
212-
// https://github.com/fabric8io/kubernetes-client/issues/3816
213-
var specDiffJsonPatch = getDiffsImpactingPathsWithPrefixes(wholeDiffJsonPatch, SPEC);
214+
boolean specMatch = match(equality, wholeDiffJsonPatch, ignoreList, SPEC);
215+
if (!specMatch) {
216+
return false;
217+
}
218+
// expect everything else to be equal
219+
var names = desiredNode.fieldNames();
220+
List<String> prefixes = new ArrayList<>();
221+
while (names.hasNext()) {
222+
String prefix = "/" + names.next();
223+
if (!NOT_OTHER_FIELDS.contains(prefix)) {
224+
prefixes.add(prefix);
225+
}
226+
}
227+
return match(true, wholeDiffJsonPatch, ignoreList, prefixes.toArray(String[]::new));
228+
}
229+
230+
private static boolean match(boolean equality, JsonNode wholeDiffJsonPatch, final List<String> ignoreList, String... prefixes) {
231+
var diffJsonPatch = getDiffsImpactingPathsWithPrefixes(wholeDiffJsonPatch, prefixes);
214232
// In case of equality is set to true, no diffs are allowed, so we return early if diffs exist
215233
// On contrary (if equality is false), "add" is allowed for cases when for some
216234
// resources Kubernetes fills-in values into spec.
217-
if (equality && !specDiffJsonPatch.isEmpty()) {
235+
if (diffJsonPatch.isEmpty()) {
236+
return true;
237+
}
238+
if (equality) {
218239
return false;
219240
}
220-
if (!equality && !ignoreList.isEmpty()) {
221-
if (!allDiffsOnIgnoreList(specDiffJsonPatch, ignoreList)) {
222-
return false;
223-
}
224-
} else {
225-
if (!allDiffsAreAddOps(specDiffJsonPatch)) {
226-
return false;
227-
}
241+
if (!ignoreList.isEmpty() && allDiffsOnIgnoreList(diffJsonPatch, ignoreList)) {
242+
return true;
228243
}
229-
return true;
244+
return allDiffsAreAddOps(diffJsonPatch);
230245
}
231246

232247
private static boolean allDiffsOnIgnoreList(List<JsonNode> metadataJSonDiffs,

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

Lines changed: 0 additions & 23 deletions
This file was deleted.

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

Lines changed: 0 additions & 22 deletions
This file was deleted.

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

Lines changed: 0 additions & 24 deletions
This file was deleted.

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

Lines changed: 0 additions & 25 deletions
This file was deleted.

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

Lines changed: 0 additions & 20 deletions
This file was deleted.

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

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,61 @@
11
package io.javaoperatorsdk.operator.processing.dependent.kubernetes.updatermatcher;
22

3-
import java.util.Map;
4-
5-
import io.fabric8.kubernetes.api.model.*;
6-
import io.fabric8.kubernetes.api.model.discovery.v1.EndpointSlice;
7-
import io.fabric8.kubernetes.api.model.rbac.ClusterRole;
8-
import io.fabric8.kubernetes.api.model.rbac.ClusterRoleBinding;
9-
import io.fabric8.kubernetes.api.model.rbac.Role;
10-
import io.fabric8.kubernetes.api.model.rbac.RoleBinding;
11-
import io.javaoperatorsdk.operator.ReconcilerUtils;
3+
import com.fasterxml.jackson.annotation.JsonInclude;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import com.fasterxml.jackson.databind.introspect.ClassIntrospector.MixInResolver;
6+
import io.fabric8.kubernetes.api.model.HasMetadata;
7+
import io.fabric8.kubernetes.api.model.KubernetesResource;
128
import io.javaoperatorsdk.operator.api.reconciler.Context;
139
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.GenericKubernetesResourceMatcher;
1410
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.ResourceUpdaterMatcher;
1511

12+
import java.util.Map;
13+
1614
public class GenericResourceUpdaterMatcher<R extends HasMetadata> implements
1715
ResourceUpdaterMatcher<R> {
1816

1917
private static final ResourceUpdaterMatcher<?> INSTANCE = new GenericResourceUpdaterMatcher<>();
2018

21-
@SuppressWarnings("rawtypes")
22-
private static final Map<Class, ResourceUpdaterMatcher> processors = Map.of(
23-
Secret.class, new SecretResourceUpdaterMatcher(),
24-
ConfigMap.class, new ConfigMapResourceUpdaterMatcher(),
25-
ServiceAccount.class, new ServiceAccountResourceUpdaterMatcher(),
26-
Role.class, new RoleResourceUpdaterMatcher(),
27-
ClusterRole.class, new ClusterRoleResourceUpdaterMatcher(),
28-
RoleBinding.class, new RoleBindingResourceUpdaterMatcher(),
29-
ClusterRoleBinding.class, new ClusterRoleBindingResourceUpdaterMatcher(),
30-
Endpoints.class, new EndpointsResourceUpdaterMatcher(),
31-
EndpointSlice.class, new EndpointSliceResourceUpdateMatcher());
19+
@JsonInclude(JsonInclude.Include.ALWAYS)
20+
private static class NullIncludingMixIn {
21+
}
22+
23+
// we need a specialized mapper so that all fields, including the null ones, can be gleaning from the desired state
24+
private static ObjectMapper MAPPER = new ObjectMapper();
25+
static {
26+
MAPPER.setMixInResolver(new MixInResolver() {
27+
28+
@Override
29+
public Class<?> findMixInClassFor(Class<?> cls) {
30+
if (KubernetesResource.class.isAssignableFrom(cls)) {
31+
return NullIncludingMixIn.class;
32+
}
33+
return null;
34+
}
35+
36+
@Override
37+
public MixInResolver copy() {
38+
return this;
39+
}
40+
});
41+
}
3242

3343
protected GenericResourceUpdaterMatcher() {}
3444

3545
@SuppressWarnings("unchecked")
3646
public static <R extends HasMetadata> ResourceUpdaterMatcher<R> updaterMatcherFor(
3747
Class<R> resourceType) {
38-
final var processor = processors.get(resourceType);
39-
return processor != null ? processor : (ResourceUpdaterMatcher<R>) INSTANCE;
48+
return (ResourceUpdaterMatcher<R>) INSTANCE;
4049
}
4150

51+
@Override
4252
public R updateResource(R actual, R desired, Context<?> context) {
43-
var clonedActual = context.getControllerConfiguration().getConfigurationService()
44-
.getResourceCloner().clone(actual);
53+
Map<String, Object> actualMap = MAPPER.convertValue(actual, Map.class);
54+
Map<String, Object> desiredMap = MAPPER.convertValue(desired, Map.class);
55+
desiredMap.remove("metadata"); // handled separately below
56+
actualMap.putAll(desiredMap); // will also preserve additionalProperties
57+
var clonedActual = (R) MAPPER.convertValue(actualMap, desired.getClass());
4558
updateLabelsAndAnnotation(clonedActual, desired);
46-
updateClonedActual(clonedActual, desired);
4759
return clonedActual;
4860
}
4961

@@ -53,15 +65,6 @@ public boolean matches(R actual, R desired, Context<?> context) {
5365
false, false, context).matched();
5466
}
5567

56-
protected void updateClonedActual(R actual, R desired) {
57-
updateSpec(actual, desired);
58-
}
59-
60-
public static <K extends HasMetadata> void updateSpec(K actual, K desired) {
61-
var desiredSpec = ReconcilerUtils.getSpec(desired);
62-
ReconcilerUtils.setSpec(actual, desiredSpec);
63-
}
64-
6568
public static <K extends HasMetadata> void updateLabelsAndAnnotation(K actual, K desired) {
6669
actual.getMetadata().getLabels().putAll(desired.getMetadata().getLabels());
6770
actual.getMetadata().getAnnotations().putAll(desired.getMetadata().getAnnotations());

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

Lines changed: 0 additions & 23 deletions
This file was deleted.

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

Lines changed: 0 additions & 19 deletions
This file was deleted.

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

Lines changed: 0 additions & 25 deletions
This file was deleted.

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

Lines changed: 0 additions & 25 deletions
This file was deleted.

0 commit comments

Comments
 (0)