Skip to content

Commit 97da6cf

Browse files
committed
generalization of the update matcher
1 parent 4110c8b commit 97da6cf

11 files changed

+69
-253
lines changed

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

Lines changed: 32 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,9 @@ 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
29+
// included we expect it to match
30+
private static Set<String> NOT_OTHER_FIELDS = Set.of(SPEC, "/metadata", "/apiVersion", "/kind");
2731

2832
private static final String PATH = "path";
2933
private static final String[] EMPTY_ARRAY = {};
@@ -192,11 +196,11 @@ public static <R extends HasMetadata, P extends HasMetadata> Result<R> match(R d
192196
}
193197
}
194198

195-
final var matched = matchSpec(actualResource, desired, specEquality, context, ignoredPaths);
199+
final var matched = match(actualResource, desired, specEquality, context, ignoredPaths);
196200
return Result.computed(matched, desired);
197201
}
198202

199-
private static <R extends HasMetadata> boolean matchSpec(R actual, R desired, boolean equality,
203+
private static <R extends HasMetadata> boolean match(R actual, R desired, boolean equality,
200204
Context<?> context,
201205
String[] ignoredPaths) {
202206

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

232249
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 & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,62 @@
22

33
import java.util.Map;
44

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;
5+
import io.fabric8.kubernetes.api.model.HasMetadata;
6+
import io.fabric8.kubernetes.api.model.KubernetesResource;
127
import io.javaoperatorsdk.operator.api.reconciler.Context;
138
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.GenericKubernetesResourceMatcher;
149
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.ResourceUpdaterMatcher;
1510

11+
import com.fasterxml.jackson.annotation.JsonInclude;
12+
import com.fasterxml.jackson.databind.ObjectMapper;
13+
import com.fasterxml.jackson.databind.introspect.ClassIntrospector.MixInResolver;
14+
1615
public class GenericResourceUpdaterMatcher<R extends HasMetadata> implements
1716
ResourceUpdaterMatcher<R> {
1817

1918
private static final ResourceUpdaterMatcher<?> INSTANCE = new GenericResourceUpdaterMatcher<>();
2019

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());
20+
@JsonInclude(JsonInclude.Include.ALWAYS)
21+
private static class NullIncludingMixIn {
22+
}
23+
24+
// we need a specialized mapper so that all fields, including the null ones, can be gleaning from
25+
// the desired state
26+
private static ObjectMapper MAPPER = new ObjectMapper();
27+
static {
28+
MAPPER.setMixInResolver(new MixInResolver() {
29+
30+
@Override
31+
public Class<?> findMixInClassFor(Class<?> cls) {
32+
if (KubernetesResource.class.isAssignableFrom(cls)) {
33+
return NullIncludingMixIn.class;
34+
}
35+
return null;
36+
}
37+
38+
@Override
39+
public MixInResolver copy() {
40+
return this;
41+
}
42+
});
43+
}
3244

3345
protected GenericResourceUpdaterMatcher() {}
3446

3547
@SuppressWarnings("unchecked")
3648
public static <R extends HasMetadata> ResourceUpdaterMatcher<R> updaterMatcherFor(
3749
Class<R> resourceType) {
38-
final var processor = processors.get(resourceType);
39-
return processor != null ? processor : (ResourceUpdaterMatcher<R>) INSTANCE;
50+
return (ResourceUpdaterMatcher<R>) INSTANCE;
4051
}
4152

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

@@ -53,15 +67,6 @@ public boolean matches(R actual, R desired, Context<?> context) {
5367
false, false, context).matched();
5468
}
5569

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-
6570
public static <K extends HasMetadata> void updateLabelsAndAnnotation(K actual, K desired) {
6671
actual.getMetadata().getLabels().putAll(desired.getMetadata().getLabels());
6772
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.

0 commit comments

Comments
 (0)