Avoid updating immutable fields.

This commit is contained in:
Michael Lipp 2025-01-25 13:32:00 +01:00
parent 9318b1279a
commit a5ddf6ac97
2 changed files with 97 additions and 4 deletions

View file

@ -18,6 +18,7 @@
package org.jdrupes.vmoperator.manager; package org.jdrupes.vmoperator.manager;
import com.google.gson.JsonObject;
import freemarker.core.ParseException; import freemarker.core.ParseException;
import freemarker.template.Configuration; import freemarker.template.Configuration;
import freemarker.template.MalformedTemplateNameException; import freemarker.template.MalformedTemplateNameException;
@ -25,6 +26,7 @@ import freemarker.template.TemplateException;
import freemarker.template.TemplateNotFoundException; import freemarker.template.TemplateNotFoundException;
import io.kubernetes.client.custom.V1Patch; import io.kubernetes.client.custom.V1Patch;
import io.kubernetes.client.openapi.ApiException; import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject;
import io.kubernetes.client.util.generic.dynamic.Dynamics; import io.kubernetes.client.util.generic.dynamic.Dynamics;
import io.kubernetes.client.util.generic.options.ListOptions; import io.kubernetes.client.util.generic.options.ListOptions;
import io.kubernetes.client.util.generic.options.PatchOptions; import io.kubernetes.client.util.generic.options.PatchOptions;
@ -41,6 +43,7 @@ import org.jdrupes.vmoperator.common.K8sV1PvcStub;
import org.jdrupes.vmoperator.manager.events.VmChannel; import org.jdrupes.vmoperator.manager.events.VmChannel;
import org.jdrupes.vmoperator.manager.events.VmDefChanged; import org.jdrupes.vmoperator.manager.events.VmDefChanged;
import org.jdrupes.vmoperator.util.DataPath; import org.jdrupes.vmoperator.util.DataPath;
import org.jdrupes.vmoperator.util.GsonPtr;
import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor; import org.yaml.snakeyaml.constructor.SafeConstructor;
@ -179,17 +182,51 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
var pvcDef = Dynamics.newFromYaml( var pvcDef = Dynamics.newFromYaml(
new Yaml(new SafeConstructor(new LoaderOptions())), out.toString()); new Yaml(new SafeConstructor(new LoaderOptions())), out.toString());
// Do apply changes // Apply changes
var pvcStub var pvcStub
= K8sV1PvcStub.get(channel.client(), vmDef.namespace(), pvcName); = K8sV1PvcStub.get(channel.client(), vmDef.namespace(), pvcName);
var pvc = pvcStub.model();
if (pvc.isEmpty()
|| !"Bound".equals(pvc.get().getStatus().getPhase())) {
// Does not exist or isn't bound, use apply
PatchOptions opts = new PatchOptions();
opts.setForce(true);
opts.setFieldManager("kubernetes-java-kubectl-apply");
if (pvcStub.patch(V1Patch.PATCH_FORMAT_APPLY_YAML,
new V1Patch(channel.client().getJSON().serialize(pvcDef)), opts)
.isEmpty()) {
logger.warning(
() -> "Could not patch pvc for " + pvcStub.name());
}
return;
}
// If bound, use json merge, omitting immutable fields
var spec = GsonPtr.to(pvcDef.getRaw()).to("spec");
spec.removeExcept("volumeAttributesClassName", "resources");
spec.access("resources").ifPresent(p -> p.removeExcept("requests"));
PatchOptions opts = new PatchOptions(); PatchOptions opts = new PatchOptions();
opts.setForce(true);
opts.setFieldManager("kubernetes-java-kubectl-apply"); opts.setFieldManager("kubernetes-java-kubectl-apply");
if (pvcStub.patch(V1Patch.PATCH_FORMAT_APPLY_YAML, if (pvcStub.patch(V1Patch.PATCH_FORMAT_JSON_MERGE_PATCH,
new V1Patch(channel.client().getJSON().serialize(pvcDef)), opts) new V1Patch(channel.client().getJSON().serialize(pvcDef)), opts)
.isEmpty()) { .isEmpty()) {
logger.warning( logger.warning(
() -> "Could not patch pvc for " + pvcStub.name()); () -> "Could not patch pvc for " + pvcStub.name());
} }
} }
@SuppressWarnings("PMD.AvoidLiteralsInIfCondition")
private void removeImmutable(DynamicKubernetesObject pvcDef) {
var spec = GsonPtr.to(pvcDef.getRaw()).to("spec").get(JsonObject.class);
for (var itr = spec.entrySet().iterator(); itr.hasNext();) {
var entry = itr.next();
if ("volumeAttributesClassName".equals(entry.getKey())) {
continue;
}
if ("resources".equals(entry.getKey())) {
continue;
}
itr.remove();
}
}
} }

View file

@ -23,6 +23,7 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive; import com.google.gson.JsonPrimitive;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -62,7 +63,8 @@ public class GsonPtr {
* @param selectors the selectors * @param selectors the selectors
* @return the Gson pointer * @return the Gson pointer
*/ */
@SuppressWarnings({ "PMD.ShortMethodName", "PMD.PreserveStackTrace" }) @SuppressWarnings({ "PMD.ShortMethodName", "PMD.PreserveStackTrace",
"PMD.AvoidDuplicateLiterals" })
public GsonPtr to(Object... selectors) { public GsonPtr to(Object... selectors) {
JsonElement element = position; JsonElement element = position;
for (Object sel : selectors) { for (Object sel : selectors) {
@ -91,6 +93,42 @@ public class GsonPtr {
return new GsonPtr(element); return new GsonPtr(element);
} }
/**
* Create a new instance pointing to the {@link JsonElement}
* selected by the given selectors. If a selector of type
* {@link String} denotes a non-existant member of a
* {@link JsonObject} the result is empty.
*
* @param selectors the selectors
* @return the Gson pointer
*/
@SuppressWarnings({ "PMD.ShortMethodName", "PMD.PreserveStackTrace" })
public Optional<GsonPtr> access(Object... selectors) {
JsonElement element = position;
for (Object sel : selectors) {
if (element instanceof JsonObject obj
&& sel instanceof String member) {
element = obj.get(member);
if (element == null) {
return Optional.empty();
}
continue;
}
if (element instanceof JsonArray arr
&& sel instanceof Integer index) {
try {
element = arr.get(index);
} catch (IndexOutOfBoundsException e) {
throw new IllegalStateException("Selected array index"
+ " may not be empty.");
}
continue;
}
throw new IllegalStateException("Invalid selection");
}
return Optional.of(new GsonPtr(element));
}
/** /**
* Returns {@link JsonElement} that the pointer points to. * Returns {@link JsonElement} that the pointer points to.
* *
@ -336,4 +374,22 @@ public class GsonPtr {
return this; return this;
} }
/**
* Removes all properties except the specified ones.
*
* @param properties the properties
*/
public void removeExcept(String... properties) {
if (!position.isJsonObject()) {
return;
}
for (var itr = ((JsonObject) position).entrySet().iterator();
itr.hasNext();) {
var entry = itr.next();
if (Arrays.asList(properties).contains(entry.getKey())) {
continue;
}
itr.remove();
}
}
} }