Skip to content

Commit 80af5c7

Browse files
committed
feat: matcher based updates for update control
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
1 parent 958d1d6 commit 80af5c7

12 files changed

Lines changed: 451 additions & 0 deletions

File tree

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright Java Operator SDK Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.javaoperatorsdk.operator.api.reconciler.matcher;
17+
18+
import io.fabric8.kubernetes.api.model.HasMetadata;
19+
import io.javaoperatorsdk.operator.api.reconciler.Context;
20+
21+
public class JsonMergePatchMatcher implements Matcher {
22+
23+
@Override
24+
public boolean matches(HasMetadata desired, HasMetadata actual, Context<?> context) {
25+
return MatcherUtils.mergePatchMatches(
26+
MatcherUtils.toNode(actual, context), MatcherUtils.toNode(desired, context));
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright Java Operator SDK Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.javaoperatorsdk.operator.api.reconciler.matcher;
17+
18+
import io.fabric8.kubernetes.api.model.HasMetadata;
19+
import io.javaoperatorsdk.operator.api.reconciler.Context;
20+
21+
public class JsonMergePatchStatusMatcher implements Matcher {
22+
23+
@Override
24+
public boolean matches(HasMetadata desired, HasMetadata actual, Context<?> context) {
25+
return MatcherUtils.mergePatchMatches(
26+
MatcherUtils.statusNode(actual, context), MatcherUtils.statusNode(desired, context));
27+
}
28+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright Java Operator SDK Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.javaoperatorsdk.operator.api.reconciler.matcher;
17+
18+
import io.fabric8.kubernetes.api.model.HasMetadata;
19+
import io.javaoperatorsdk.operator.api.reconciler.Context;
20+
21+
public class JsonPatchMacher implements Matcher {
22+
23+
@Override
24+
public boolean matches(HasMetadata desired, HasMetadata actual, Context<?> context) {
25+
return MatcherUtils.jsonPatchMatches(
26+
MatcherUtils.toNode(actual, context), MatcherUtils.toNode(desired, context));
27+
}
28+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright Java Operator SDK Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.javaoperatorsdk.operator.api.reconciler.matcher;
17+
18+
import io.fabric8.kubernetes.api.model.HasMetadata;
19+
import io.javaoperatorsdk.operator.api.reconciler.Context;
20+
21+
public class JsonPatchStatusMacher implements Matcher {
22+
23+
@Override
24+
public boolean matches(HasMetadata desired, HasMetadata actual, Context<?> context) {
25+
return MatcherUtils.jsonPatchMatches(
26+
MatcherUtils.statusNode(actual, context), MatcherUtils.statusNode(desired, context));
27+
}
28+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright Java Operator SDK Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.javaoperatorsdk.operator.api.reconciler.matcher;
17+
18+
import io.fabric8.kubernetes.api.model.HasMetadata;
19+
import io.javaoperatorsdk.operator.api.reconciler.Context;
20+
21+
public interface Matcher {
22+
23+
/**
24+
* Matcher if the desired state matches the actual state in respective to underlying update/patch
25+
* method
26+
*/
27+
boolean matches(HasMetadata desired, HasMetadata actual, Context<?> context);
28+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright Java Operator SDK Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.javaoperatorsdk.operator.api.reconciler.matcher;
17+
18+
import io.fabric8.kubernetes.api.model.HasMetadata;
19+
import io.fabric8.zjsonpatch.JsonDiff;
20+
import io.javaoperatorsdk.operator.api.reconciler.Context;
21+
22+
import com.fasterxml.jackson.databind.JsonNode;
23+
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
24+
import com.fasterxml.jackson.databind.node.NullNode;
25+
import com.fasterxml.jackson.databind.node.ObjectNode;
26+
27+
/** Shared helpers for the client-side patch based {@link Matcher} implementations. */
28+
final class MatcherUtils {
29+
30+
private static final String STATUS = "status";
31+
32+
private MatcherUtils() {}
33+
34+
static JsonNode toNode(HasMetadata resource, Context<?> context) {
35+
return context.getClient().getKubernetesSerialization().convertValue(resource, JsonNode.class);
36+
}
37+
38+
/** The {@code status} subresource as a node, or a {@link NullNode} if it is not present. */
39+
static JsonNode statusNode(HasMetadata resource, Context<?> context) {
40+
var status = toNode(resource, context).get(STATUS);
41+
return status == null ? NullNode.getInstance() : status;
42+
}
43+
44+
/**
45+
* @return {@code true} if applying the desired state as a JSON Patch (RFC 6902) to the actual
46+
* state would be a no-op, i.e. the computed patch contains no operations.
47+
*/
48+
static boolean jsonPatchMatches(JsonNode actual, JsonNode desired) {
49+
return JsonDiff.asJson(actual, desired).isEmpty();
50+
}
51+
52+
/**
53+
* @return {@code true} if applying the desired state as a JSON Merge Patch (RFC 7386) to the
54+
* actual state would be a no-op, i.e. every value present in the desired state already equals
55+
* the actual state (additional values only present in the actual state are allowed).
56+
*/
57+
static boolean mergePatchMatches(JsonNode actual, JsonNode desired) {
58+
return applyMergePatch(actual.deepCopy(), desired).equals(actual);
59+
}
60+
61+
// See https://datatracker.ietf.org/doc/html/rfc7386#section-2
62+
private static JsonNode applyMergePatch(JsonNode target, JsonNode patch) {
63+
if (!patch.isObject()) {
64+
return patch;
65+
}
66+
var targetObject =
67+
target.isObject() ? (ObjectNode) target : JsonNodeFactory.instance.objectNode();
68+
var fields = patch.fields();
69+
while (fields.hasNext()) {
70+
var entry = fields.next();
71+
if (entry.getValue().isNull()) {
72+
targetObject.remove(entry.getKey());
73+
} else {
74+
var current = targetObject.get(entry.getKey());
75+
targetObject.set(
76+
entry.getKey(),
77+
applyMergePatch(current == null ? NullNode.getInstance() : current, entry.getValue()));
78+
}
79+
}
80+
return targetObject;
81+
}
82+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright Java Operator SDK Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.javaoperatorsdk.operator.api.reconciler.matcher;
17+
18+
import io.fabric8.kubernetes.api.model.HasMetadata;
19+
import io.javaoperatorsdk.operator.api.reconciler.Context;
20+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.SSABasedGenericKubernetesResourceMatcher;
21+
22+
public class SSAMatcher implements Matcher {
23+
24+
@Override
25+
public boolean matches(HasMetadata desired, HasMetadata actual, Context<?> context) {
26+
return SSABasedGenericKubernetesResourceMatcher.getInstance().matches(actual, desired, context);
27+
}
28+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright Java Operator SDK Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.javaoperatorsdk.operator.api.reconciler.matcher;
17+
18+
import io.fabric8.kubernetes.api.model.HasMetadata;
19+
import io.javaoperatorsdk.operator.api.reconciler.Context;
20+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.GenericKubernetesResourceMatcher;
21+
22+
public class SSAStatusMatcher implements Matcher {
23+
24+
@Override
25+
public boolean matches(HasMetadata desired, HasMetadata actual, Context<?> context) {
26+
return GenericKubernetesResourceMatcher.matchStatus(desired, actual, context).matched();
27+
}
28+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright Java Operator SDK Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.javaoperatorsdk.operator.api.reconciler.matcher;
17+
18+
import io.fabric8.kubernetes.api.model.HasMetadata;
19+
import io.javaoperatorsdk.operator.api.reconciler.Context;
20+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.GenericKubernetesResourceMatcher;
21+
22+
public class UpdateMatcher implements Matcher {
23+
24+
@Override
25+
public boolean matches(HasMetadata desired, HasMetadata actual, Context<?> context) {
26+
return GenericKubernetesResourceMatcher.match(desired, actual, context).matched();
27+
}
28+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright Java Operator SDK Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.javaoperatorsdk.operator.api.reconciler.matcher;
17+
18+
import io.fabric8.kubernetes.api.model.HasMetadata;
19+
import io.javaoperatorsdk.operator.api.reconciler.Context;
20+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.GenericKubernetesResourceMatcher;
21+
22+
public class UpdateStatusMatcher implements Matcher {
23+
24+
@Override
25+
public boolean matches(HasMetadata desired, HasMetadata actual, Context<?> context) {
26+
return GenericKubernetesResourceMatcher.matchStatus(desired, actual, context).matched();
27+
}
28+
}

0 commit comments

Comments
 (0)