Skip to content

Commit 5b673a4

Browse files
authored
feat: allow easier configuration of matcher (#2760)
* feat: allow easier configuration of matcher Signed-off-by: Chris Laprun <[email protected]> * refactor: avoid recording default matcher Signed-off-by: Chris Laprun <[email protected]> * refactor: make matcher configurable instead of settable Signed-off-by: Chris Laprun <[email protected]> --------- Signed-off-by: Chris Laprun <[email protected]>
1 parent 820210c commit 5b673a4

File tree

6 files changed

+74
-6
lines changed

6 files changed

+74
-6
lines changed

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

+19
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,24 @@ boolean createResourceOnlyIfNotExistingWithSSA() default
2727
*/
2828
BooleanWithUndefined useSSA() default BooleanWithUndefined.UNDEFINED;
2929

30+
/**
31+
* The underlying Informer event source configuration
32+
*
33+
* @return the {@link Informer} configuration
34+
*/
3035
Informer informer() default @Informer;
36+
37+
/**
38+
* The specific matcher implementation to use when Server-Side Apply (SSA) is used, when case the
39+
* default one isn't working appropriately. Typically, this could be needed to cover border cases
40+
* with some Kubernetes resources that are modified by their controllers to normalize or add
41+
* default values, which could result in infinite loops with the default matcher. Using a specific
42+
* matcher could also be an optimization decision if determination of whether two resources match
43+
* can be done faster than what can be done with the default exhaustive algorithm.
44+
*
45+
* @return the class of the specific matcher to use for the associated dependent resources
46+
* @since 5.1
47+
*/
48+
Class<? extends SSABasedGenericKubernetesResourceMatcher> matcher() default
49+
SSABasedGenericKubernetesResourceMatcher.class;
3150
}

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

+13-1
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,22 @@ public KubernetesDependentResourceConfig<R> configFrom(
2222
DEFAULT_CREATE_RESOURCE_ONLY_IF_NOT_EXISTING_WITH_SSA;
2323

2424
Boolean useSSA = null;
25+
SSABasedGenericKubernetesResourceMatcher<R> matcher =
26+
SSABasedGenericKubernetesResourceMatcher.getInstance();
2527
if (configAnnotation != null) {
2628
createResourceOnlyIfNotExistingWithSSA =
2729
configAnnotation.createResourceOnlyIfNotExistingWithSSA();
2830
useSSA = configAnnotation.useSSA().asBoolean();
31+
32+
// check if we have a specific matcher
33+
Class<? extends KubernetesDependentResource<?, ?>> dependentResourceClass =
34+
(Class<? extends KubernetesDependentResource<?, ?>>) spec.getDependentResourceClass();
35+
final var context =
36+
Utils.contextFor(
37+
controllerConfig, dependentResourceClass, configAnnotation.annotationType());
38+
matcher =
39+
Utils.instantiate(
40+
configAnnotation.matcher(), SSABasedGenericKubernetesResourceMatcher.class, context);
2941
}
3042

3143
var informerConfiguration =
@@ -35,7 +47,7 @@ public KubernetesDependentResourceConfig<R> configFrom(
3547
controllerConfig);
3648

3749
return new KubernetesDependentResourceConfig<>(
38-
useSSA, createResourceOnlyIfNotExistingWithSSA, informerConfiguration);
50+
useSSA, createResourceOnlyIfNotExistingWithSSA, informerConfiguration, matcher);
3951
}
4052

4153
@SuppressWarnings({"unchecked"})

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

+4-1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public abstract class KubernetesDependentResource<R extends HasMetadata, P exten
3737
implements ConfiguredDependentResource<KubernetesDependentResourceConfig<R>> {
3838

3939
private static final Logger log = LoggerFactory.getLogger(KubernetesDependentResource.class);
40+
4041
private final boolean garbageCollected = this instanceof GarbageCollected;
4142
private KubernetesDependentResourceConfig<R> kubernetesDependentResourceConfig;
4243
private volatile Boolean useSSA;
@@ -112,7 +113,9 @@ public Result<R> match(R actualResource, R desired, P primary, Context<P> contex
112113
addMetadata(true, actualResource, desired, primary, context);
113114
if (useSSA(context)) {
114115
matches =
115-
SSABasedGenericKubernetesResourceMatcher.getInstance()
116+
configuration()
117+
.map(KubernetesDependentResourceConfig::matcher)
118+
.orElse(SSABasedGenericKubernetesResourceMatcher.getInstance())
116119
.matches(actualResource, desired, context);
117120
} else {
118121
matches =

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

+9-1
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,18 @@ public class KubernetesDependentResourceConfig<R extends HasMetadata> {
1010
private final Boolean useSSA;
1111
private final boolean createResourceOnlyIfNotExistingWithSSA;
1212
private final InformerConfiguration<R> informerConfig;
13+
private final SSABasedGenericKubernetesResourceMatcher<R> matcher;
1314

1415
public KubernetesDependentResourceConfig(
1516
Boolean useSSA,
1617
boolean createResourceOnlyIfNotExistingWithSSA,
17-
InformerConfiguration<R> informerConfig) {
18+
InformerConfiguration<R> informerConfig,
19+
SSABasedGenericKubernetesResourceMatcher<R> matcher) {
1820
this.useSSA = useSSA;
1921
this.createResourceOnlyIfNotExistingWithSSA = createResourceOnlyIfNotExistingWithSSA;
2022
this.informerConfig = informerConfig;
23+
this.matcher =
24+
matcher != null ? matcher : SSABasedGenericKubernetesResourceMatcher.getInstance();
2125
}
2226

2327
public boolean createResourceOnlyIfNotExistingWithSSA() {
@@ -31,4 +35,8 @@ public Boolean useSSA() {
3135
public InformerConfiguration<R> informerConfig() {
3236
return informerConfig;
3337
}
38+
39+
public SSABasedGenericKubernetesResourceMatcher<R> matcher() {
40+
return matcher;
41+
}
3442
}

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

+8-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ public final class KubernetesDependentResourceConfigBuilder<R extends HasMetadat
88
private boolean createResourceOnlyIfNotExistingWithSSA;
99
private Boolean useSSA = null;
1010
private InformerConfiguration<R> informerConfiguration;
11+
private SSABasedGenericKubernetesResourceMatcher<R> matcher;
1112

1213
public KubernetesDependentResourceConfigBuilder() {}
1314

@@ -29,8 +30,14 @@ public KubernetesDependentResourceConfigBuilder<R> withKubernetesDependentInform
2930
return this;
3031
}
3132

33+
public KubernetesDependentResourceConfigBuilder<R> withSSAMatcher(
34+
SSABasedGenericKubernetesResourceMatcher<R> matcher) {
35+
this.matcher = matcher;
36+
return this;
37+
}
38+
3239
public KubernetesDependentResourceConfig<R> build() {
3340
return new KubernetesDependentResourceConfig<>(
34-
useSSA, createResourceOnlyIfNotExistingWithSSA, informerConfiguration);
41+
useSSA, createResourceOnlyIfNotExistingWithSSA, informerConfiguration, matcher);
3542
}
3643
}

operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcherTest.java

+21-2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import io.javaoperatorsdk.operator.api.reconciler.Context;
2323

2424
import static org.assertj.core.api.Assertions.assertThat;
25+
import static org.mockito.ArgumentMatchers.any;
2526
import static org.mockito.Mockito.mock;
2627
import static org.mockito.Mockito.when;
2728

@@ -39,6 +40,7 @@ void setup() {
3940
when(mockedContext.getClient()).thenReturn(client);
4041

4142
final var configurationService = mock(ConfigurationService.class);
43+
when(configurationService.shouldUseSSA(any(), any(), any())).thenReturn(true);
4244
final var controllerConfiguration = mock(ControllerConfiguration.class);
4345
when(controllerConfiguration.getConfigurationService()).thenReturn(configurationService);
4446
when(controllerConfiguration.fieldManager()).thenReturn("controller");
@@ -239,17 +241,34 @@ void testSanitizeState_daemonSetWithResources_withMismatch() {
239241
@ParameterizedTest
240242
@ValueSource(booleans = {true, false})
241243
void testCustomMatcher_returnsExpectedMatchBasedOnReadOnlyLabel(boolean readOnly) {
244+
var dr = new ConfigMapDR();
245+
dr.configureWith(
246+
new KubernetesDependentResourceConfigBuilder()
247+
.withSSAMatcher(new ReadOnlyAwareMatcher())
248+
.build());
242249
var desiredConfigMap =
243250
loadResource("configmap.empty-owner-reference-desired.yaml", ConfigMap.class);
244251
desiredConfigMap.getData().put("key1", "another value");
245252
var actualConfigMap = loadResource("configmap.empty-owner-reference.yaml", ConfigMap.class);
246253
actualConfigMap.getMetadata().getLabels().put("readonly", Boolean.toString(readOnly));
247254

248-
var matcher = new ReadOnlyAwareMatcher<ConfigMap>();
249-
assertThat(matcher.matches(actualConfigMap, desiredConfigMap, mockedContext))
255+
ConfigMap ignoredPrimary = null;
256+
assertThat(
257+
dr.match(
258+
actualConfigMap,
259+
desiredConfigMap,
260+
ignoredPrimary,
261+
(Context<ConfigMap>) mockedContext)
262+
.matched())
250263
.isEqualTo(readOnly);
251264
}
252265

266+
private static class ConfigMapDR extends KubernetesDependentResource<ConfigMap, ConfigMap> {
267+
public ConfigMapDR() {
268+
super(ConfigMap.class);
269+
}
270+
}
271+
253272
private static class ReadOnlyAwareMatcher<T extends HasMetadata>
254273
extends SSABasedGenericKubernetesResourceMatcher<T> {
255274
@Override

0 commit comments

Comments
 (0)