Skip to content

Commit 8cd5785

Browse files
committed
skeleton of the update
1 parent 48f9a99 commit 8cd5785

File tree

3 files changed

+100
-20
lines changed

3 files changed

+100
-20
lines changed

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

Lines changed: 6 additions & 0 deletions
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 configurable
270+
default boolean useLegacyCreateUpdateAndMatchingOnDependentResources() {
271+
return false;
272+
}
267273
}

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

Lines changed: 39 additions & 9 deletions
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+
.useLegacyCreateUpdateAndMatchingOnDependentResources()) {
140+
return prepare(target, primary, "Creating").create();
141+
} else {
142+
return prepare(target, primary, "Creating").patch(SSA_PATCH_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+
.useLegacyCreateUpdateAndMatchingOnDependentResources()) {
149+
var updatedActual = processor.replaceSpecOnActual(actual, target, context);
150+
return prepare(updatedActual, primary, "Updating").replace();
151+
} else {
152+
return prepare(actual, primary, "Updating").patch(SSA_PATCH_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+
.useLegacyCreateUpdateAndMatchingOnDependentResources()) {
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) {

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

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,32 @@
1010
import io.fabric8.kubernetes.api.model.ManagedFieldsEntry;
1111
import io.javaoperatorsdk.operator.api.reconciler.Context;
1212

13+
import com.fasterxml.jackson.core.JsonProcessingException;
1314
import com.fasterxml.jackson.core.type.TypeReference;
1415
import com.fasterxml.jackson.databind.ObjectMapper;
1516

1617
public class SSABasedGenericKubernetesResourceMatcher<R extends HasMetadata> {
1718

19+
@SuppressWarnings("rawtypes")
20+
private static final SSABasedGenericKubernetesResourceMatcher INSTANCE =
21+
new SSABasedGenericKubernetesResourceMatcher<>();
22+
23+
@SuppressWarnings("unchecked")
24+
public static <L extends HasMetadata> SSABasedGenericKubernetesResourceMatcher<L> getInstance() {
25+
return INSTANCE;
26+
}
27+
28+
private static final TypeReference<HashMap<String, Object>> typeRef = new TypeReference<>() {};
29+
1830
private static final String F_PREFIX = "f:";
1931
private static final String K_PREFIX = "k:";
20-
21-
public static final String METADATA_LABELS = "/metadata/labels";
22-
public static final String METADATA_ANNOTATIONS = "/metadata/annotations";
32+
public static final String METADATA_KEY = "metadata";
2333

2434
private static final Logger log =
2535
LoggerFactory.getLogger(SSABasedGenericKubernetesResourceMatcher.class);
2636

37+
// todo list orders should be irrelevant, change lists to sets?
38+
// todo owner reference removal?
2739
public boolean matches(R actual, R desired, Context<?> context) {
2840

2941
var optionalManagedFieldsEntry =
@@ -33,20 +45,35 @@ public boolean matches(R actual, R desired, Context<?> context) {
3345
if (optionalManagedFieldsEntry.isEmpty()) {
3446
return false;
3547
}
48+
3649
var managedFieldsEntry = optionalManagedFieldsEntry.orElseThrow();
3750

3851
var objectMapper =
3952
context.getControllerConfiguration().getConfigurationService().getObjectMapper();
4053

41-
TypeReference<HashMap<String, Object>> typeRef = new TypeReference<>() {};
4254
var actualMap = objectMapper.convertValue(actual, typeRef);
4355
var desiredMap = objectMapper.convertValue(desired, typeRef);
4456

4557
var prunedActual = new HashMap<String, Object>();
4658
pruneActualAccordingManagedFields(prunedActual, actualMap,
4759
managedFieldsEntry.getFieldsV1().getAdditionalProperties(), objectMapper);
60+
removeIrrelevantValues(desiredMap);
61+
62+
log.trace("Pruned actual: \n {} \n desired: \n {} ", prunedActual, desiredMap);
63+
64+
return prunedActual.equals(desiredMap);
65+
}
66+
67+
private void removeIrrelevantValues(HashMap<String, Object> desiredMap) {
68+
var metadata = (Map<String, Object>) desiredMap.get(METADATA_KEY);
69+
metadata.remove("name");
70+
metadata.remove("namespace");
71+
if (metadata.isEmpty()) {
72+
desiredMap.remove(METADATA_KEY);
73+
}
74+
desiredMap.remove("kind");
75+
desiredMap.remove("apiVersion");
4876

49-
return actualMap.equals(desiredMap);
5077
}
5178

5279
private void pruneActualAccordingManagedFields(Map<String, Object> result,
@@ -71,9 +98,6 @@ private void pruneActualAccordingManagedFields(Map<String, Object> result,
7198
pruneActualAccordingManagedFields(emptyResMapValue, actualListEntry,
7299
(Map<String, Object>) listEntry.getValue(), objectMapper);
73100
}
74-
75-
// todo handle lists
76-
return;
77101
} else {
78102
var emptyMapValue = new HashMap<String, Object>();
79103
result.put(targetKey, emptyMapValue);
@@ -87,16 +111,36 @@ private void pruneActualAccordingManagedFields(Map<String, Object> result,
87111
} else {
88112
result.put(targetKey, actualMap.get(targetKey));
89113
}
114+
} else {
115+
if (!".".equals(key)) {
116+
throw new IllegalStateException("Key: " + key + " has no prefix: " + F_PREFIX);
117+
}
90118
}
91119
}
92120

93121
}
94122

95-
private Map<String, Object> selectListEntryBasedOnKey(String key, List<Map<String, Object>> value,
123+
private Map<String, Object> selectListEntryBasedOnKey(String key,
124+
List<Map<String, Object>> values,
96125
ObjectMapper objectMapper) {
97-
// objectMapper.
126+
try {
127+
Map<String, Object> ids = objectMapper.readValue(key, typeRef);
128+
var possibleTargets =
129+
values.stream().filter(v -> v.entrySet().containsAll(ids.entrySet()))
130+
.collect(Collectors.toList());
131+
if (possibleTargets.isEmpty()) {
132+
throw new IllegalStateException(
133+
"Cannot find list element for key:" + key + ", in map: " + values);
134+
}
135+
if (possibleTargets.size() > 1) {
136+
throw new IllegalStateException(
137+
"More targets found in list element for key:" + key + ", in map: " + values);
138+
}
98139

99-
return null;
140+
return possibleTargets.get(0);
141+
} catch (JsonProcessingException e) {
142+
throw new IllegalStateException(e);
143+
}
100144
}
101145

102146

0 commit comments

Comments
 (0)