Skip to content

Commit 9d88d0e

Browse files
shawkinscsviri
andcommitted
generalization of the update matcher (#2014)
* feat: create resource only if not exists (#2001) * feat: leader election callbacks (#2015) * discriminator improvements (#2013) * generalization of the update matcher * consolidating the diffs also adding a test that removes a field not present in the desired --------- Co-authored-by: Attila Mészáros <[email protected]> Signed-off-by: csviri <[email protected]> Signed-off-by: Attila Mészáros <[email protected]>
1 parent ba009e8 commit 9d88d0e

13 files changed

+70
-326
lines changed

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

+25-86
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
package io.javaoperatorsdk.operator.processing.dependent.kubernetes;
22

3-
import java.util.ArrayList;
43
import java.util.Arrays;
54
import java.util.Collections;
65
import java.util.List;
7-
import java.util.Objects;
8-
import java.util.Optional;
96

107
import io.fabric8.kubernetes.api.model.ConfigMap;
118
import io.fabric8.kubernetes.api.model.HasMetadata;
@@ -20,8 +17,10 @@ public class GenericKubernetesResourceMatcher<R extends HasMetadata, P extends H
2017
implements Matcher<R, P> {
2118

2219
private static final String SPEC = "/spec";
20+
private static final String METADATA = "/metadata";
2321
private static final String ADD = "add";
2422
private static final String OP = "op";
23+
private static final List<String> IGNORED_FIELDS = List.of("/apiVersion", "/kind", "/status");
2524
public static final String METADATA_LABELS = "/metadata/labels";
2625
public static final String METADATA_ANNOTATIONS = "/metadata/annotations";
2726

@@ -184,76 +183,39 @@ public static <R extends HasMetadata, P extends HasMetadata> Result<R> match(R d
184183
"Equality should be false in case of ignore list provided");
185184
}
186185

187-
if (considerMetadata) {
188-
Optional<Result<R>> res =
189-
matchMetadata(desired, actualResource, labelsAndAnnotationsEquality, context);
190-
if (res.isPresent()) {
191-
return res.orElseThrow();
192-
}
193-
}
194-
195-
final var matched = matchSpec(actualResource, desired, specEquality, context, ignoreList);
196-
return Result.computed(matched, desired);
197-
}
198-
199-
private static <R extends HasMetadata> boolean matchSpec(R actual, R desired, boolean equality,
200-
Context<?> context, List<String> ignoreList) {
201186
final var kubernetesSerialization = context.getClient().getKubernetesSerialization();
202187
var desiredNode = kubernetesSerialization.convertValue(desired, JsonNode.class);
203-
var actualNode = kubernetesSerialization.convertValue(actual, JsonNode.class);
188+
var actualNode = kubernetesSerialization.convertValue(actualResource, JsonNode.class);
204189
var wholeDiffJsonPatch = JsonDiff.asJson(desiredNode, actualNode);
205190

206-
// reflection will be replaced by this:
207-
// https://github.com/fabric8io/kubernetes-client/issues/3816
208-
var specDiffJsonPatch = getDiffsImpactingPathsWithPrefixes(wholeDiffJsonPatch, SPEC);
209-
// In case of equality is set to true, no diffs are allowed, so we return early if diffs exist
210-
// On contrary (if equality is false), "add" is allowed for cases when for some
211-
// resources Kubernetes fills-in values into spec.
212-
if (equality && !specDiffJsonPatch.isEmpty()) {
213-
return false;
214-
}
215-
if (!equality && !ignoreList.isEmpty()) {
216-
return allDiffsOnIgnoreList(specDiffJsonPatch, ignoreList);
217-
} else {
218-
return allDiffsAreAddOps(specDiffJsonPatch);
191+
boolean matched = true;
192+
for (int i = 0; i < wholeDiffJsonPatch.size() && matched; i++) {
193+
var node = wholeDiffJsonPatch.get(i);
194+
if (nodeIsChildOf(node, List.of(SPEC))) {
195+
matched = match(specEquality, node, ignoreList);
196+
} else if (nodeIsChildOf(node, List.of(METADATA))) {
197+
// conditionally consider labels and annotations
198+
if (considerMetadata
199+
&& nodeIsChildOf(node, List.of(METADATA_LABELS, METADATA_ANNOTATIONS))) {
200+
matched = match(labelsAndAnnotationsEquality, node, Collections.emptyList());
201+
}
202+
} else if (!nodeIsChildOf(node, IGNORED_FIELDS)) {
203+
matched = match(true, node, ignoreList);
204+
}
219205
}
206+
207+
return Result.computed(matched, desired);
220208
}
221209

222-
private static boolean allDiffsOnIgnoreList(List<JsonNode> metadataJSonDiffs,
223-
List<String> ignoreList) {
224-
if (metadataJSonDiffs.isEmpty()) {
210+
private static boolean match(boolean equality, JsonNode diff,
211+
final List<String> ignoreList) {
212+
if (equality) {
225213
return false;
226214
}
227-
return metadataJSonDiffs.stream().allMatch(n -> nodeIsChildOf(n, ignoreList));
228-
}
229-
230-
private static <R extends HasMetadata, P extends HasMetadata> Optional<Result<R>> matchMetadata(
231-
R desired,
232-
R actualResource,
233-
boolean labelsAndAnnotationsEquality, Context<P> context) {
234-
if (labelsAndAnnotationsEquality) {
235-
final var desiredMetadata = desired.getMetadata();
236-
final var actualMetadata = actualResource.getMetadata();
237-
238-
final var matched =
239-
Objects.equals(desiredMetadata.getAnnotations(), actualMetadata.getAnnotations()) &&
240-
Objects.equals(desiredMetadata.getLabels(), actualMetadata.getLabels());
241-
if (!matched) {
242-
return Optional.of(Result.computed(false, desired));
243-
}
244-
} else {
245-
final var objectMapper = context.getClient().getKubernetesSerialization();
246-
var desiredNode = objectMapper.convertValue(desired, JsonNode.class);
247-
var actualNode = objectMapper.convertValue(actualResource, JsonNode.class);
248-
var wholeDiffJsonPatch = JsonDiff.asJson(desiredNode, actualNode);
249-
var metadataJSonDiffs = getDiffsImpactingPathsWithPrefixes(wholeDiffJsonPatch,
250-
METADATA_LABELS,
251-
METADATA_ANNOTATIONS);
252-
if (!allDiffsAreAddOps(metadataJSonDiffs)) {
253-
return Optional.of(Result.computed(false, desired));
254-
}
215+
if (!ignoreList.isEmpty()) {
216+
return nodeIsChildOf(diff, ignoreList);
255217
}
256-
return Optional.empty();
218+
return ADD.equals(diff.get(OP).asText());
257219
}
258220

259221
static boolean nodeIsChildOf(JsonNode n, List<String> prefixes) {
@@ -265,29 +227,6 @@ static String getPath(JsonNode n) {
265227
return n.get(PATH).asText();
266228
}
267229

268-
static boolean allDiffsAreAddOps(List<JsonNode> metadataJSonDiffs) {
269-
if (metadataJSonDiffs.isEmpty()) {
270-
return true;
271-
}
272-
return metadataJSonDiffs.stream().allMatch(n -> ADD.equals(n.get(OP).asText()));
273-
}
274-
275-
public static List<JsonNode> getDiffsImpactingPathsWithPrefixes(JsonNode diffJsonPatch,
276-
String... prefixes) {
277-
if (prefixes != null && prefixes.length > 0) {
278-
var res = new ArrayList<JsonNode>();
279-
var prefixList = Arrays.asList(prefixes);
280-
for (int i = 0; i < diffJsonPatch.size(); i++) {
281-
var node = diffJsonPatch.get(i);
282-
if (nodeIsChildOf(node, prefixList)) {
283-
res.add(node);
284-
}
285-
}
286-
return res;
287-
}
288-
return Collections.emptyList();
289-
}
290-
291230
@Deprecated(forRemoval = true)
292231
public static <R extends HasMetadata, P extends HasMetadata> Result<R> match(
293232
KubernetesDependentResource<R, P> dependentResource, R actualResource, P primary,

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

-23
This file was deleted.

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

-22
This file was deleted.

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

-24
This file was deleted.

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

-25
This file was deleted.

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

-20
This file was deleted.

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

+16-33
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,40 @@
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.client.utils.KubernetesSerialization;
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

1611
public class GenericResourceUpdaterMatcher<R extends HasMetadata> implements
1712
ResourceUpdaterMatcher<R> {
1813

14+
private static final String METADATA = "metadata";
1915
private static final ResourceUpdaterMatcher<?> INSTANCE = new GenericResourceUpdaterMatcher<>();
2016

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());
32-
3317
protected GenericResourceUpdaterMatcher() {}
3418

3519
@SuppressWarnings("unchecked")
3620
public static <R extends HasMetadata> ResourceUpdaterMatcher<R> updaterMatcherFor(
3721
Class<R> resourceType) {
38-
final var processor = processors.get(resourceType);
39-
return processor != null ? processor : (ResourceUpdaterMatcher<R>) INSTANCE;
22+
return (ResourceUpdaterMatcher<R>) INSTANCE;
4023
}
4124

25+
@SuppressWarnings("unchecked")
26+
@Override
4227
public R updateResource(R actual, R desired, Context<?> context) {
43-
var clonedActual = context.getControllerConfiguration().getConfigurationService()
44-
.getResourceCloner().clone(actual);
28+
KubernetesSerialization kubernetesSerialization =
29+
context.getClient().getKubernetesSerialization();
30+
Map<String, Object> actualMap = kubernetesSerialization.convertValue(actual, Map.class);
31+
Map<String, Object> desiredMap = kubernetesSerialization.convertValue(desired, Map.class);
32+
// replace all top level fields from actual with desired, but merge metadata separately
33+
var metadata = actualMap.remove(METADATA);
34+
actualMap.replaceAll((k, v) -> desiredMap.get(k));
35+
actualMap.putAll(desiredMap);
36+
actualMap.put(METADATA, metadata);
37+
var clonedActual = (R) kubernetesSerialization.convertValue(actualMap, desired.getClass());
4538
updateLabelsAndAnnotation(clonedActual, desired);
46-
updateClonedActual(clonedActual, desired);
4739
return clonedActual;
4840
}
4941

@@ -53,15 +45,6 @@ public boolean matches(R actual, R desired, Context<?> context) {
5345
false, false, context).matched();
5446
}
5547

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-
6548
public static <K extends HasMetadata> void updateLabelsAndAnnotation(K actual, K desired) {
6649
actual.getMetadata().getLabels().putAll(desired.getMetadata().getLabels());
6750
actual.getMetadata().getAnnotations().putAll(desired.getMetadata().getAnnotations());

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

-23
This file was deleted.

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

-19
This file was deleted.

0 commit comments

Comments
 (0)