diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcher.java index 9952763e6a..352e4ee419 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcher.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcher.java @@ -29,11 +29,40 @@ static Matcher matcherFor( @Override public Result match(R actualResource, P primary, Context

context) { var desired = dependentResource.desired(primary, context); - return match(desired, actualResource, false); + return match(desired, actualResource, false, false); } public static Result match(R desired, R actualResource, boolean considerMetadata) { + return match(desired, actualResource, considerMetadata, false); + } + + /** + * Determines whether the specified actual resource matches the specified desired resource, + * possibly considering metadata and deeper equality checks. + * + * @param desired the desired resource + * @param actualResource the actual resource + * @param considerMetadata {@code true} if labels and annotations will be checked for equality, + * {@code false} otherwise (meaning that metadata changes will be ignored for matching + * purposes) + * @param equality if {@code false}, the algorithm checks if the properties in the desired + * resource spec are same as in the actual resource spec. The reason is that admission + * controllers and default Kubernetes controllers might add default values to some + * properties which are not set in the desired resources' spec and comparing it with simple + * equality check would mean that such resource will not match (while conceptually should). + * However, there is an issue with this for example if desired spec contains a list of + * values and a value is removed, this still will match the actual state from previous + * reconciliation. Setting this parameter to {@code true}, will match the resources only if + * all properties and values are equal. This could be implemented also by overriding equals + * method of spec, should be done as an optimization - this implementation does not require + * that. + * + * @return results of matching + * @param resource + */ + public static Result match(R desired, R actualResource, + boolean considerMetadata, boolean equality) { if (considerMetadata) { final var desiredMetadata = desired.getMetadata(); final var actualMetadata = actualResource.getMetadata(); @@ -61,6 +90,12 @@ public static Result match(R desired, R actualResourc var desiredSpecNode = objectMapper.valueToTree(ReconcilerUtils.getSpec(desired)); var actualSpecNode = objectMapper.valueToTree(ReconcilerUtils.getSpec(actualResource)); var diffJsonPatch = JsonDiff.asJson(desiredSpecNode, actualSpecNode); + // In case of equality is set to true, no diffs are allowed, so we return early if diffs exist + // On contrary (if equality is false), "add" is allowed for cases when for some + // resources Kubernetes fills-in values into spec. + if (equality && diffJsonPatch.size() > 0) { + return Result.computed(false, desired); + } for (int i = 0; i < diffJsonPatch.size(); i++) { String operation = diffJsonPatch.get(i).get("op").asText(); if (!operation.equals("add")) { @@ -90,10 +125,17 @@ public static Result match(R desired, R actualResourc * @param

the type of primary resources associated with the secondary resources we want to * match */ + public static Result match( + KubernetesDependentResource dependentResource, R actualResource, P primary, + Context

context, boolean considerMetadata, boolean strongEquality) { + final var desired = dependentResource.desired(primary, context); + return match(desired, actualResource, considerMetadata, strongEquality); + } + public static Result match( KubernetesDependentResource dependentResource, R actualResource, P primary, Context

context, boolean considerMetadata) { final var desired = dependentResource.desired(primary, context); - return match(desired, actualResource, considerMetadata); + return match(desired, actualResource, considerMetadata, false); } } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcherTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcherTest.java index d39db6ced6..91a71067ca 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcherTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcherTest.java @@ -43,6 +43,11 @@ void checksIfDesiredValuesAreTheSame() { .withFailMessage("Additive changes should be ok") .isTrue(); + assertThat(GenericKubernetesResourceMatcher + .match(dependentResource, actual, null, context, true, true).matched()) + .withFailMessage("Strong equality does not ignore additive changes on spec") + .isFalse(); + actual = createDeployment(); assertThat(matcher.match(actual, createPrimary("removed"), context).matched()) .withFailMessage("Removed value should not be ok")