Skip to content

Commit c61be33

Browse files
committed
feat: SSA based dependent resource matching and create/update
1 parent 825a6c1 commit c61be33

File tree

36 files changed

+1435
-22
lines changed

36 files changed

+1435
-22
lines changed

cm.yaml

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
apiVersion: v1
2+
kind: ConfigMap
3+
metadata:
4+
name: test1
5+
namespace: default
6+
ownerReferences:
7+
- apiVersion: v1
8+
kind: ConfigMap
9+
name: kube-root-ca.crt
10+
uid: 1ef74cb4-dbbd-45ef-9caf-aa76186594ea
11+
data:
12+
key1: "val1"
13+
# key2: "val2"
14+

cm2.yaml

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
apiVersion: v1
2+
kind: ConfigMap
3+
metadata:
4+
name: test1
5+
namespace: default
6+
data:
7+
key3: "val3"
8+
9+

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java

+6
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
import com.fasterxml.jackson.databind.ObjectMapper;
3535

36+
import static io.javaoperatorsdk.operator.api.config.ControllerConfiguration.DEFAULT_FIELD_MANAGER;
3637
import static io.javaoperatorsdk.operator.api.reconciler.Constants.DEFAULT_NAMESPACES_SET;
3738

3839
public class BaseConfigurationService extends AbstractConfigurationService {
@@ -135,6 +136,11 @@ protected <P extends HasMetadata> ControllerConfiguration<P> configFor(Reconcile
135136
timeUnit = reconciliationInterval.timeUnit();
136137
}
137138

139+
final var dependentFieldManager = valueOrDefault(
140+
annotation,
141+
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::fieldManager,
142+
DEFAULT_FIELD_MANAGER);
143+
138144
final var config = new ResolvedControllerConfiguration<P>(
139145
resourceClass, name, generationAware,
140146
associatedReconcilerClass, retry, rateLimiter,

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java

+6
Original file line numberDiff line numberDiff line change
@@ -264,4 +264,10 @@ static ConfigurationService newOverriddenConfigurationService(
264264
default ExecutorServiceManager getExecutorServiceManager() {
265265
return new ExecutorServiceManager(this);
266266
}
267+
268+
// todo test transition
269+
// todo javadoc
270+
default boolean legacyCreateUpdateAndMatchingForDependentResources() {
271+
return false;
272+
}
267273
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationServiceOverrider.java

+14
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public class ConfigurationServiceOverrider {
3232
private Boolean stopOnInformerErrorDuringStartup;
3333
private Duration cacheSyncTimeout;
3434
private ResourceClassResolver resourceClassResolver;
35+
private Boolean legacyCreateUpdateAndMatchingForDependentResources;
3536

3637
ConfigurationServiceOverrider(ConfigurationService original) {
3738
this.original = original;
@@ -139,6 +140,12 @@ public ConfigurationServiceOverrider withResourceClassResolver(
139140
return this;
140141
}
141142

143+
public ConfigurationServiceOverrider withLegacyCreateUpdateAndMatchingForDependentResources(
144+
boolean value) {
145+
this.legacyCreateUpdateAndMatchingForDependentResources = value;
146+
return this;
147+
}
148+
142149
public ConfigurationService build() {
143150
return new BaseConfigurationService(original.getVersion(), cloner, objectMapper) {
144151
@Override
@@ -248,6 +255,13 @@ public ResourceClassResolver getResourceClassResolver() {
248255
return resourceClassResolver != null ? resourceClassResolver
249256
: super.getResourceClassResolver();
250257
}
258+
259+
@Override
260+
public boolean legacyCreateUpdateAndMatchingForDependentResources() {
261+
return legacyCreateUpdateAndMatchingForDependentResources != null
262+
? legacyCreateUpdateAndMatchingForDependentResources
263+
: super.legacyCreateUpdateAndMatchingForDependentResources();
264+
}
251265
};
252266
}
253267

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java

+8
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ public interface ControllerConfiguration<P extends HasMetadata> extends Resource
2222

2323
@SuppressWarnings("rawtypes")
2424
RateLimiter DEFAULT_RATE_LIMITER = LinearRateLimiter.deactivatedRateLimiter();
25+
String DEFAULT_FIELD_MANAGER = "controller";
26+
String FABRIC8_CLIENT_DEFAULT_FIELD_MANAGER = "fabric8-kubernetes-client";
2527

2628
default String getName() {
2729
return ensureValidName(null, getAssociatedReconcilerClassName());
@@ -124,4 +126,10 @@ default Class<P> getResourceClass() {
124126
default Set<String> getEffectiveNamespaces() {
125127
return ResourceConfiguration.super.getEffectiveNamespaces(getConfigurationService());
126128
}
129+
130+
// todo javadoc
131+
default String fieldManager() {
132+
return DEFAULT_FIELD_MANAGER;
133+
}
134+
127135
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public class ControllerConfigurationOverrider<R extends HasMetadata> {
3939
private Map<DependentResourceSpec, Object> configurations;
4040
private ItemStore<R> itemStore;
4141
private String name;
42+
private String fieldManager;
4243

4344
private ControllerConfigurationOverrider(ControllerConfiguration<R> original) {
4445
this.finalizer = original.getFinalizerName();
@@ -54,6 +55,7 @@ private ControllerConfigurationOverrider(ControllerConfiguration<R> original) {
5455
this.original = original;
5556
this.rateLimiter = original.getRateLimiter();
5657
this.name = original.getName();
58+
this.fieldManager = original.fieldManager();
5759
}
5860

5961
public ControllerConfigurationOverrider<R> withFinalizer(String finalizer) {
@@ -168,6 +170,12 @@ public ControllerConfigurationOverrider<R> withName(String name) {
168170
return this;
169171
}
170172

173+
public ControllerConfigurationOverrider<R> withFieldManager(
174+
String dependentFieldManager) {
175+
this.fieldManager = dependentFieldManager;
176+
return this;
177+
}
178+
171179
public ControllerConfigurationOverrider<R> replacingNamedDependentResourceConfig(String name,
172180
Object dependentResourceConfig) {
173181

@@ -190,7 +198,7 @@ public ControllerConfiguration<R> build() {
190198
generationAware, original.getAssociatedReconcilerClassName(), retry, rateLimiter,
191199
reconciliationMaxInterval, onAddFilter, onUpdateFilter, genericFilter,
192200
original.getDependentResources(),
193-
namespaces, finalizer, labelSelector, configurations, itemStore,
201+
namespaces, finalizer, labelSelector, configurations, itemStore, fieldManager,
194202
original.getConfigurationService());
195203
overridden.setEventFilter(customResourcePredicate);
196204
return overridden;

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResolvedControllerConfiguration.java

+14-3
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public class ResolvedControllerConfiguration<P extends HasMetadata>
3232
private final Map<DependentResourceSpec, Object> configurations;
3333
private final ItemStore<P> itemStore;
3434
private final ConfigurationService configurationService;
35+
private final String fieldManager;
3536

3637
private ResourceEventFilter<P> eventFilter;
3738
private List<DependentResourceSpec> dependentResources;
@@ -44,7 +45,8 @@ public ResolvedControllerConfiguration(Class<P> resourceClass, ControllerConfigu
4445
other.genericFilter().orElse(null),
4546
other.getDependentResources(), other.getNamespaces(),
4647
other.getFinalizerName(), other.getLabelSelector(), Collections.emptyMap(),
47-
other.getItemStore().orElse(null), other.getConfigurationService());
48+
other.getItemStore().orElse(null), other.fieldManager(),
49+
other.getConfigurationService());
4850
}
4951

5052
public static Duration getMaxReconciliationInterval(long interval, TimeUnit timeUnit) {
@@ -72,10 +74,12 @@ public ResolvedControllerConfiguration(Class<P> resourceClass, String name,
7274
List<DependentResourceSpec> dependentResources,
7375
Set<String> namespaces, String finalizer, String labelSelector,
7476
Map<DependentResourceSpec, Object> configurations, ItemStore<P> itemStore,
77+
String fieldManager,
7578
ConfigurationService configurationService) {
7679
this(resourceClass, name, generationAware, associatedReconcilerClassName, retry, rateLimiter,
7780
maxReconciliationInterval, onAddFilter, onUpdateFilter, genericFilter,
78-
namespaces, finalizer, labelSelector, configurations, itemStore, configurationService);
81+
namespaces, finalizer, labelSelector, configurations, itemStore, fieldManager,
82+
configurationService);
7983
setDependentResources(dependentResources);
8084
}
8185

@@ -85,6 +89,7 @@ protected ResolvedControllerConfiguration(Class<P> resourceClass, String name,
8589
OnAddFilter<P> onAddFilter, OnUpdateFilter<P> onUpdateFilter, GenericFilter<P> genericFilter,
8690
Set<String> namespaces, String finalizer, String labelSelector,
8791
Map<DependentResourceSpec, Object> configurations, ItemStore<P> itemStore,
92+
String fieldManager,
8893
ConfigurationService configurationService) {
8994
super(resourceClass, namespaces, labelSelector, onAddFilter, onUpdateFilter, genericFilter,
9095
itemStore);
@@ -99,13 +104,14 @@ protected ResolvedControllerConfiguration(Class<P> resourceClass, String name,
99104
this.itemStore = itemStore;
100105
this.finalizer =
101106
ControllerConfiguration.ensureValidFinalizerName(finalizer, getResourceTypeName());
107+
this.fieldManager = fieldManager;
102108
}
103109

104110
protected ResolvedControllerConfiguration(Class<P> resourceClass, String name,
105111
Class<? extends Reconciler> reconcilerClas, ConfigurationService configurationService) {
106112
this(resourceClass, name, false, getAssociatedReconcilerClassName(reconcilerClas), null, null,
107113
null, null, null, null, null,
108-
null, null, null, null, configurationService);
114+
null, null, null, null, null, configurationService);
109115
}
110116

111117
@Override
@@ -182,4 +188,9 @@ public Object getConfigurationFor(DependentResourceSpec spec) {
182188
public Optional<ItemStore<P>> getItemStore() {
183189
return Optional.ofNullable(itemStore);
184190
}
191+
192+
@Override
193+
public String fieldManager() {
194+
return fieldManager;
195+
}
185196
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Context.java

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.util.Optional;
44
import java.util.Set;
55
import java.util.concurrent.ExecutorService;
6+
import java.util.concurrent.ExecutorService;
67
import java.util.stream.Stream;
78

89
import io.fabric8.kubernetes.api.model.HasMetadata;

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java

+5
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import io.javaoperatorsdk.operator.processing.retry.GenericRetry;
1818
import io.javaoperatorsdk.operator.processing.retry.Retry;
1919

20+
import static io.javaoperatorsdk.operator.api.config.ControllerConfiguration.DEFAULT_FIELD_MANAGER;
21+
2022
@Inherited
2123
@Retention(RetentionPolicy.RUNTIME)
2224
@Target({ElementType.TYPE})
@@ -129,4 +131,7 @@ MaxReconciliationInterval maxReconciliationInterval() default @MaxReconciliation
129131
Class<? extends RateLimiter> rateLimiter() default LinearRateLimiter.class;
130132

131133
Class<? extends ItemStore> itemStore() default ItemStore.class;
134+
135+
// todo javadoc
136+
String fieldManager() default DEFAULT_FIELD_MANAGER;
132137
}

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ public class GenericKubernetesResourceMatcher<R extends HasMetadata, P extends H
1919

2020
private static final String ADD = "add";
2121
private static final String OP = "op";
22-
private static final String METADATA_LABELS = "/metadata/labels";
23-
private static final String METADATA_ANNOTATIONS = "/metadata/annotations";
22+
public static final String METADATA_LABELS = "/metadata/labels";
23+
public static final String METADATA_ANNOTATIONS = "/metadata/annotations";
2424

2525
private static final String PATH = "path";
2626
private static final String[] EMPTY_ARRAY = {};

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

+47-9
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import io.fabric8.kubernetes.api.model.Namespaced;
1212
import io.fabric8.kubernetes.client.KubernetesClient;
1313
import io.fabric8.kubernetes.client.dsl.Resource;
14+
import io.fabric8.kubernetes.client.dsl.base.PatchContext;
15+
import io.fabric8.kubernetes.client.dsl.base.PatchType;
1416
import io.javaoperatorsdk.operator.OperatorException;
1517
import io.javaoperatorsdk.operator.api.config.dependent.Configured;
1618
import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration;
@@ -38,12 +40,17 @@ public abstract class KubernetesDependentResource<R extends HasMetadata, P exten
3840
DependentResourceConfigurator<KubernetesDependentResourceConfig<R>> {
3941

4042
private static final Logger log = LoggerFactory.getLogger(KubernetesDependentResource.class);
43+
private static final PatchContext SSA_PATCH_CONTEXT = new PatchContext.Builder()
44+
.withPatchType(PatchType.SERVER_SIDE_APPLY)
45+
.withForce(true)
46+
.build();
4147

4248
protected KubernetesClient client;
4349
private final ResourceUpdatePreProcessor<R> processor;
4450
private final boolean garbageCollected = this instanceof GarbageCollected;
4551
private KubernetesDependentResourceConfig<R> kubernetesDependentResourceConfig;
4652

53+
4754
@SuppressWarnings("unchecked")
4855
public KubernetesDependentResource(Class<R> resourceType) {
4956
super(resourceType);
@@ -128,16 +135,35 @@ protected R handleUpdate(R actual, R desired, P primary, Context<P> context) {
128135

129136
@SuppressWarnings("unused")
130137
public R create(R target, P primary, Context<P> context) {
131-
return prepare(target, primary, "Creating").create();
138+
if (context.getControllerConfiguration().getConfigurationService()
139+
.legacyCreateUpdateAndMatchingForDependentResources()) {
140+
return prepare(target, primary, "Creating").create();
141+
} else {
142+
return prepare(target, primary, "Creating").patch(getSSAPatchContext(context));
143+
}
132144
}
133145

134146
public R update(R actual, R target, P primary, Context<P> context) {
135-
var updatedActual = processor.replaceSpecOnActual(actual, target, context);
136-
return prepare(updatedActual, primary, "Updating").replace();
147+
if (context.getControllerConfiguration().getConfigurationService()
148+
.legacyCreateUpdateAndMatchingForDependentResources()) {
149+
var updatedActual = processor.replaceSpecOnActual(actual, target, context);
150+
return prepare(updatedActual, primary, "Updating").replace();
151+
} else {
152+
return prepare(target, primary, "Updating").patch(getSSAPatchContext(context));
153+
}
137154
}
138155

139156
public Result<R> match(R actualResource, P primary, Context<P> context) {
140-
return GenericKubernetesResourceMatcher.match(this, actualResource, primary, context, false);
157+
if (context.getControllerConfiguration().getConfigurationService()
158+
.legacyCreateUpdateAndMatchingForDependentResources()) {
159+
return GenericKubernetesResourceMatcher.match(this, actualResource, primary, context, false);
160+
} else {
161+
final var desired = desired(primary, context);
162+
addReferenceHandlingMetadata(desired, primary);
163+
var matches = SSABasedGenericKubernetesResourceMatcher.getInstance().matches(actualResource,
164+
desired, context);
165+
return Result.computed(matches, desired);
166+
}
141167
}
142168

143169
@SuppressWarnings("unused")
@@ -164,11 +190,7 @@ protected Resource<R> prepare(R desired, P primary, String actionName) {
164190
desired.getClass(),
165191
ResourceID.fromResource(desired));
166192

167-
if (addOwnerReference()) {
168-
desired.addOwnerReference(primary);
169-
} else if (useDefaultAnnotationsToIdentifyPrimary()) {
170-
addDefaultSecondaryToPrimaryMapperAnnotations(desired, primary);
171-
}
193+
addReferenceHandlingMetadata(desired, primary);
172194

173195
if (desired instanceof Namespaced) {
174196
return client.resource(desired).inNamespace(desired.getMetadata().getNamespace());
@@ -177,6 +199,14 @@ protected Resource<R> prepare(R desired, P primary, String actionName) {
177199
}
178200
}
179201

202+
protected void addReferenceHandlingMetadata(R desired, P primary) {
203+
if (addOwnerReference()) {
204+
desired.addOwnerReference(primary);
205+
} else if (useDefaultAnnotationsToIdentifyPrimary()) {
206+
addDefaultSecondaryToPrimaryMapperAnnotations(desired, primary);
207+
}
208+
}
209+
180210
@Override
181211
@SuppressWarnings("unchecked")
182212
protected InformerEventSource<R, P> createEventSource(EventSourceContext<P> context) {
@@ -260,4 +290,12 @@ public boolean isDeletable() {
260290
return super.isDeletable() && !garbageCollected;
261291
}
262292

293+
private PatchContext getSSAPatchContext(Context<P> context) {
294+
return new PatchContext.Builder()
295+
.withPatchType(PatchType.SERVER_SIDE_APPLY)
296+
.withForce(true)
297+
.withFieldManager(context.getControllerConfiguration().fieldManager())
298+
.build();
299+
}
300+
263301
}

0 commit comments

Comments
 (0)