Use statefulset for runner.

This commit is contained in:
Michael Lipp 2023-08-09 15:19:29 +02:00
parent b79ddcf05c
commit 4058fa6bda
10 changed files with 185 additions and 420 deletions

View file

@ -139,13 +139,13 @@ data:
<#if disk.volumeClaimTemplate??
&& disk.volumeClaimTemplate.metadata??
&& disk.volumeClaimTemplate.metadata.name??>
<#assign name = disk.volumeClaimTemplate.metadata.name.asString>
<#assign diskName = disk.volumeClaimTemplate.metadata.name.asString + "-disk">
<#else>
<#assign name = "" + drvCounter>
<#assign diskName = "disk-" + drvCounter>
</#if>
<#if disk.volumeClaimTemplate??>
- type: raw
resource: /dev/disk-${ name }
resource: /dev/${ diskName }
<#if disk.bootindex??>
bootindex: ${ disk.bootindex.asInt?c }
</#if>

View file

@ -1,16 +0,0 @@
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
namespace: ${ cr.metadata.namespace.asString }
name: ${ cr.metadata.name.asString + "-runner-data" }
labels:
app.kubernetes.io/name: ${ constants.APP_NAME }
app.kubernetes.io/instance: ${ cr.metadata.name.asString }
app.kubernetes.io/managed-by: ${ constants.VM_OP_NAME }
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Mi

View file

@ -1,94 +0,0 @@
kind: Pod
apiVersion: v1
metadata:
namespace: ${ cr.metadata.namespace.asString }
name: ${ cr.metadata.name.asString }
labels:
app.kubernetes.io/name: ${ constants.APP_NAME }
app.kubernetes.io/instance: ${ cr.metadata.name.asString }
app.kubernetes.io/managed-by: ${ constants.VM_OP_NAME }
annotations:
# Triggers update of config map mounted in pod
# See https://ahmet.im/blog/kubernetes-secret-volumes-delay/
vmrunner.jdrupes.org/cmVersion: "${ cm.metadata.resourceVersion.asString }"
spec:
containers:
- name: ${ cr.metadata.name.asString }
<#assign image = cr.spec.image>
image: ${ image.repository.asString }/${ image.path.asString }:${ image.version.asString }
resources: {}
imagePullPolicy: ${ image.pullPolicy.asString }
volumeMounts:
# Not needed because pod is priviledged:
# - mountPath: /dev/kvm
# name: dev-kvm
# - mountPath: /dev/net/tun
# name: dev-tun
# - mountPath: /sys/fs/cgroup
# name: cgroup
- name: config
mountPath: /etc/opt/vmrunner
- name: vm-data
mountPath: /var/local/vm-data
- name: vmop-image-repository
mountPath: ${ constants.IMAGE_REPO_PATH }
volumeDevices:
<#assign diskCounter = 0/>
<#list cr.spec.vm.disks.asList() as disk>
<#if disk.volumeClaimTemplate??>
<#if disk.volumeClaimTemplate.metadata??
&& disk.volumeClaimTemplate.metadata.name??>
<#assign diskName = "disk-" + disk.volumeClaimTemplate.metadata.name.asString>
<#else>
<#assign diskName = "disk-" + diskCounter>
</#if>
- name: ${ diskName }
devicePath: /dev/${ diskName }
<#assign diskCounter = diskCounter + 1/>
</#if>
</#list>
securityContext:
privileged: true
volumes:
# Not needed because pod is priviledged:
# - name: dev-kvm
# hostPath:
# path: /dev/kvm
# type: CharDevice
# - hostPath:
# path: /dev/net/tun
# type: CharDevice
# name: dev-tun
# - name: cgroup
# hostPath:
# path: /sys/fs/cgroup
- name: config
configMap:
name: ${ cr.metadata.name.asString }
- name: vm-data
persistentVolumeClaim:
claimName: ${ cr.metadata.name.asString }-runner-data
- name: vmop-image-repository
persistentVolumeClaim:
claimName: vmop-image-repository
<#assign diskCounter = 0/>
<#list cr.spec.vm.disks.asList() as disk>
<#if disk.volumeClaimTemplate??>
<#if disk.volumeClaimTemplate.metadata??
&& disk.volumeClaimTemplate.metadata.name??>
<#assign claimName = disk.volumeClaimTemplate.metadata.name.asString>
<#assign diskName = "disk-" + claimName>
<#else>
<#assign claimName = cr.metadata.name.asString + "-disk-" + diskCounter>
<#assign diskName = "disk-" + diskCounter>
</#if>
- name: ${ diskName }
persistentVolumeClaim:
claimName: ${ claimName }
<#assign diskCounter = diskCounter + 1/>
</#if>
</#list>
hostNetwork: true
terminationGracePeriodSeconds: ${ (cr.spec.vm.powerdownTimeout.asInt + 5)?c }
restartPolicy: Never

View file

@ -0,0 +1,126 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
namespace: ${ cr.metadata.namespace.asString }
name: ${ cr.metadata.name.asString }
labels:
app.kubernetes.io/name: ${ constants.APP_NAME }
app.kubernetes.io/instance: ${ cr.metadata.name.asString }
app.kubernetes.io/managed-by: ${ constants.VM_OP_NAME }
spec:
selector:
matchLabels:
app.kubernetes.io/name: ${ constants.APP_NAME }
app.kubernetes.io/instance: ${ cr.metadata.name.asString }
replicas: ${ (cr.spec.vm.state.asString == "Running")?then(1, 0) }
template:
metadata:
namespace: ${ cr.metadata.namespace.asString }
name: ${ cr.metadata.name.asString }
labels:
app.kubernetes.io/name: ${ constants.APP_NAME }
app.kubernetes.io/instance: ${ cr.metadata.name.asString }
app.kubernetes.io/managed-by: ${ constants.VM_OP_NAME }
annotations:
# Triggers update of config map mounted in pod
# See https://ahmet.im/blog/kubernetes-secret-volumes-delay/
vmrunner.jdrupes.org/cmVersion: "${ cm.metadata.resourceVersion.asString }"
spec:
containers:
- name: ${ cr.metadata.name.asString }
<#assign image = cr.spec.image>
image: ${ image.repository.asString }/${ image.path.asString }:${ image.version.asString }
resources: {}
imagePullPolicy: ${ image.pullPolicy.asString }
volumeMounts:
# Not needed because pod is priviledged:
# - mountPath: /dev/kvm
# name: dev-kvm
# - mountPath: /dev/net/tun
# name: dev-tun
# - mountPath: /sys/fs/cgroup
# name: cgroup
- name: config
mountPath: /etc/opt/vmrunner
- name: runner-data
mountPath: /var/local/vm-data
- name: vmop-image-repository
mountPath: ${ constants.IMAGE_REPO_PATH }
volumeDevices:
<#assign diskCounter = 0/>
<#list cr.spec.vm.disks.asList() as disk>
<#if disk.volumeClaimTemplate??>
<#if disk.volumeClaimTemplate.metadata??
&& disk.volumeClaimTemplate.metadata.name??>
<#assign diskName = disk.volumeClaimTemplate.metadata.name.asString + "-disk">
<#else>
<#assign diskName = "disk-" + diskCounter>
</#if>
- name: ${ diskName }
devicePath: /dev/${ diskName }
<#assign diskCounter = diskCounter + 1/>
</#if>
</#list>
securityContext:
privileged: true
volumes:
# Not needed because pod is priviledged:
# - name: dev-kvm
# hostPath:
# path: /dev/kvm
# type: CharDevice
# - hostPath:
# path: /dev/net/tun
# type: CharDevice
# name: dev-tun
# - name: cgroup
# hostPath:
# path: /sys/fs/cgroup
- name: config
configMap:
name: ${ cr.metadata.name.asString }
- name: vmop-image-repository
persistentVolumeClaim:
claimName: vmop-image-repository
hostNetwork: true
terminationGracePeriodSeconds: ${ (cr.spec.vm.powerdownTimeout.asInt + 5)?c }
volumeClaimTemplates:
- metadata:
namespace: ${ cr.metadata.namespace.asString }
name: runner-data
labels:
app.kubernetes.io/name: ${ constants.APP_NAME }
app.kubernetes.io/instance: ${ cr.metadata.name.asString }
app.kubernetes.io/managed-by: ${ constants.VM_OP_NAME }
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Mi
<#assign diskCounter = 0/>
<#list cr.spec.vm.disks.asList() as disk>
<#if disk.volumeClaimTemplate??>
<#if disk.volumeClaimTemplate.metadata??
&& disk.volumeClaimTemplate.metadata.name??>
<#assign diskName = disk.volumeClaimTemplate.metadata.name.asString + "-disk">
<#else>
<#assign diskName = "disk-" + diskCounter>
</#if>
- metadata:
namespace: ${ cr.metadata.namespace.asString }
name: ${ diskName }
labels:
app.kubernetes.io/name: ${ constants.APP_NAME }
app.kubernetes.io/instance: ${ cr.metadata.name.asString }
app.kubernetes.io/managed-by: ${ constants.VM_OP_NAME }
<#if disk.volumeClaimTemplate.metadata??
&& disk.volumeClaimTemplate.metadata.annotations??>
annotations:
${ disk.volumeClaimTemplate.metadata.annotations.toString() }
</#if>
spec:
${ disk.volumeClaimTemplate.spec.toString() }
<#assign diskCounter = diskCounter + 1/>
</#if>
</#list>

View file

@ -1,86 +0,0 @@
/*
* VM-Operator
* Copyright (C) 2023 Michael N. Lipp
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jdrupes.vmoperator.manager;
import com.google.gson.JsonObject;
import freemarker.template.Configuration;
import freemarker.template.TemplateException;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesApi;
import io.kubernetes.client.util.generic.dynamic.Dynamics;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Map;
/**
* Delegee for reconciling the data PVC
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
/* default */ class DataReconciler {
private final Configuration fmConfig;
/**
* Instantiates a new config map reconciler.
*
* @param fmConfig the fm config
*/
public DataReconciler(Configuration fmConfig) {
this.fmConfig = fmConfig;
}
/**
* Reconcile.
*
* @param model the model
* @param channel the channel
* @throws TemplateException the template exception
* @throws ApiException the api exception
* @throws IOException Signals that an I/O exception has occurred.
*/
@SuppressWarnings("PMD.ConfusingTernary")
public void reconcile(Map<String, Object> model, VmChannel channel)
throws TemplateException, ApiException, IOException {
// Combine template and data and parse result
var fmTemplate = fmConfig.getTemplate("runnerDataPvc.ftl.yaml");
StringWriter out = new StringWriter();
fmTemplate.process(model, out);
// Avoid Yaml.load due to
// https://github.com/kubernetes-client/java/issues/2741
var pvcDef = Dynamics.newFromYaml(out.toString());
// Get API and check if PVC exists
DynamicKubernetesApi pvcApi = new DynamicKubernetesApi("", "v1",
"persistentvolumeclaims", channel.client());
var existing = K8s.get(pvcApi, pvcDef.getMetadata());
// If PVC does not exist, create. Else patch (apply)
if (existing.isEmpty()) {
pvcApi.create(pvcDef);
} else {
// spec is immutable, so mix in existing spec
GsonPtr.to(pvcDef.getRaw()).set("spec", GsonPtr
.to(existing.get().getRaw()).get(JsonObject.class, "spec")
.get().deepCopy());
K8s.apply(pvcApi, existing.get(),
channel.client().getJSON().serialize(pvcDef));
}
}
}

View file

@ -1,128 +0,0 @@
/*
* VM-Operator
* Copyright (C) 2023 Michael N. Lipp
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jdrupes.vmoperator.manager;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesApi;
import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject;
import io.kubernetes.client.util.generic.options.ListOptions;
import java.util.Collections;
import static org.jdrupes.vmoperator.manager.Constants.APP_NAME;
import static org.jdrupes.vmoperator.manager.Constants.VM_OP_NAME;
/**
* Delegee for reconciling the PVCs for the disks
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
/* default */ class DisksReconciler {
/**
* Reconcile disks.
*
* @param vmDef the vm def
* @param channel the channel
* @throws ApiException the api exception
*/
public void reconcile(JsonObject vmDef,
VmChannel channel) throws ApiException {
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
var disks = GsonPtr.to(vmDef)
.get(JsonArray.class, "spec", "vm", "disks")
.map(JsonArray::asList).orElse(Collections.emptyList());
int index = 0;
for (var disk : disks) {
reconcileDisk(vmDef, index++, (JsonObject) disk, channel);
}
}
@SuppressWarnings({ "PMD.AvoidDuplicateLiterals", "PMD.ConfusingTernary" })
private void reconcileDisk(JsonObject vmDefinition,
int index, JsonObject diskDef, VmChannel channel)
throws ApiException {
if (!diskDef.has("volumeClaimTemplate")) {
return;
}
var pvcObject = new DynamicKubernetesObject();
var pvcRaw = GsonPtr.to(pvcObject.getRaw());
var vmRaw = GsonPtr.to(vmDefinition);
var pvcTpl = GsonPtr.to(diskDef).to("volumeClaimTemplate");
// Copy base and metadata from template and add missing/additional data.
pvcObject.setApiVersion(pvcTpl.getAsString("apiVersion").get());
pvcObject.setKind(pvcTpl.getAsString("kind").get());
var vmName = vmRaw.getAsString("metadata", "name").orElse("default");
pvcRaw.get(JsonObject.class).add("metadata",
pvcTpl.to("metadata").get(JsonObject.class).deepCopy());
var defMeta = pvcRaw.to("metadata");
defMeta.computeIfAbsent("namespace", () -> new JsonPrimitive(
vmRaw.getAsString("metadata", "namespace").orElse("default")));
defMeta.computeIfAbsent("name", () -> new JsonPrimitive(
vmName + "-disk-" + index));
var pvcLbls = pvcRaw.to("metadata", "labels");
pvcLbls.set("app.kubernetes.io/name", APP_NAME);
pvcLbls.set("app.kubernetes.io/instance", vmName);
pvcLbls.set("app.kubernetes.io/component", "disk");
pvcLbls.set("app.kubernetes.io/managed-by", VM_OP_NAME);
// Get API and check if PVC exists
DynamicKubernetesApi pvcApi = new DynamicKubernetesApi("", "v1",
"persistentvolumeclaims", channel.client());
var existing = K8s.get(pvcApi, pvcObject.getMetadata());
// If PVC does not exist, create. Else patch (apply)
if (existing.isEmpty()) {
// PVC does not exist yet, copy spec from template
pvcRaw.get(JsonObject.class).add("spec",
pvcTpl.to("spec").get(JsonObject.class).deepCopy());
pvcApi.create(pvcObject);
} else {
// spec is immutable, so mix in existing spec
pvcRaw.set("spec", GsonPtr.to(existing.get().getRaw())
.to("spec").get().deepCopy());
K8s.apply(pvcApi, existing.get(),
channel.client().getJSON().serialize(pvcObject));
}
}
/**
* Delete the PVCs generated from the defined disks.
*
* @param event the event
* @param channel the channel
* @throws ApiException the api exception
*/
public void deleteDisks(VmDefChanged event, VmChannel channel)
throws ApiException {
// Get API and check and list related
var pvcApi = K8s.pvcApi(channel.client());
var pvcs = pvcApi.list(event.object().getMetadata().getNamespace(),
new ListOptions().labelSelector(
"app.kubernetes.io/managed-by=" + VM_OP_NAME
+ ",app.kubernetes.io/name=" + APP_NAME
+ ",app.kubernetes.io/instance="
+ event.object().getMetadata().getName()));
for (var pvc : pvcs.getObject().getItems()) {
K8s.delete(pvcApi, pvc);
}
}
}

View file

@ -50,9 +50,7 @@ public class Reconciler extends Component {
@SuppressWarnings("PMD.SingularField")
private final Configuration fmConfig;
private final CmReconciler cmReconciler;
private final DataReconciler dataReconciler;
private final DisksReconciler disksReconciler;
private final PodReconciler podReconciler;
private final StsReconciler stsReconciler;
/**
* Instantiates a new reconciler.
@ -73,9 +71,7 @@ public class Reconciler extends Component {
fmConfig.setClassForTemplateLoading(Reconciler.class, "");
cmReconciler = new CmReconciler(fmConfig);
disksReconciler = new DisksReconciler();
dataReconciler = new DataReconciler(fmConfig);
podReconciler = new PodReconciler(fmConfig);
stsReconciler = new StsReconciler(fmConfig);
}
/**
@ -120,15 +116,12 @@ public class Reconciler extends Component {
// Reconcile
if (event.type() != Type.DELETED) {
dataReconciler.reconcile(model, channel);
disksReconciler.reconcile(vmDef, channel);
var configMap = cmReconciler.reconcile(event, model, channel);
model.put("cm", configMap.getRaw());
podReconciler.reconcile(event, model, channel);
stsReconciler.reconcile(event, model, channel);
} else {
podReconciler.reconcile(event, model, channel);
stsReconciler.reconcile(event, model, channel);
cmReconciler.reconcile(event, model, channel);
disksReconciler.deleteDisks(event, channel);
}
}

View file

@ -25,18 +25,17 @@ import io.kubernetes.client.custom.V1Patch;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesApi;
import io.kubernetes.client.util.generic.dynamic.Dynamics;
import io.kubernetes.client.util.generic.options.DeleteOptions;
import io.kubernetes.client.util.generic.options.PatchOptions;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Map;
import static org.jdrupes.vmoperator.manager.Constants.STATE_STOPPED;
import org.jdrupes.vmoperator.manager.VmDefChanged.Type;
/**
* Delegee for reconciling the pod.
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
/* default */ class PodReconciler {
/* default */ class StsReconciler {
private final Configuration fmConfig;
@ -45,12 +44,12 @@ import org.jdrupes.vmoperator.manager.VmDefChanged.Type;
*
* @param fmConfig the fm config
*/
public PodReconciler(Configuration fmConfig) {
public StsReconciler(Configuration fmConfig) {
this.fmConfig = fmConfig;
}
/**
* Reconcile pod.
* Reconcile stateful set.
*
* @param event the event
* @param model the model
@ -63,51 +62,38 @@ import org.jdrupes.vmoperator.manager.VmDefChanged.Type;
VmChannel channel)
throws IOException, TemplateException, ApiException {
// Check if exists
DynamicKubernetesApi podApi = new DynamicKubernetesApi("", "v1",
"pods", channel.client());
var existing = K8s.get(podApi, event.object().getMetadata());
DynamicKubernetesApi stsApi = new DynamicKubernetesApi("apps", "v1",
"statefulsets", channel.client());
// var existing = K8s.get(stsApi, event.object().getMetadata());
// Get desired state.
var delete = event.type() == Type.DELETED
|| GsonPtr.to((JsonObject) model.get("cr")).to("spec", "vm")
.getAsString("state").orElse("").equals(STATE_STOPPED);
// If deleted or stopped, delete
if (delete) {
if (existing.isPresent()) {
var opts = new DeleteOptions();
opts.setGracePeriodSeconds(
GsonPtr.to((JsonObject) model.get("cr")).to("spec", "vm")
.getAsLong("powerdownTimeout").get() + 1);
K8s.delete(podApi, existing.get(), opts);
}
if (event.type() == Type.DELETED) {
var meta = GsonPtr.to((JsonObject) model.get("cr")).to("metadata");
PatchOptions opts = new PatchOptions();
opts.setFieldManager("kubernetes-java-kubectl-apply");
stsApi.patch(meta.getAsString("namespace").get(),
meta.getAsString("name").get(), V1Patch.PATCH_FORMAT_JSON_PATCH,
new V1Patch("[{\"op\": \"replace\", "
+ "\"path\": \"/spec/replicas\", \"value\": 0}]"),
opts).throwsApiException();
stsApi.delete(meta.getAsString("namespace").get(),
meta.getAsString("name").get()).throwsApiException();
return;
}
// Combine template and data and parse result
var fmTemplate = fmConfig.getTemplate("runnerPod.ftl.yaml");
var fmTemplate = fmConfig.getTemplate("runnerSts.ftl.yaml");
StringWriter out = new StringWriter();
fmTemplate.process(model, out);
// Avoid Yaml.load due to
// https://github.com/kubernetes-client/java/issues/2741
var podDef = Dynamics.newFromYaml(out.toString());
// Check if update
if (existing.isEmpty()) {
podApi.create(podDef);
} else {
// only annotations are updated
var metadata = new JsonObject();
metadata.add("annotations", GsonPtr.to(podDef.getRaw())
.to("metadata").get(JsonObject.class, "annotations").get());
var patch = new JsonObject();
patch.add("metadata", metadata);
podApi.patch(existing.get().getMetadata().getNamespace(),
existing.get().getMetadata().getName(),
V1Patch.PATCH_FORMAT_JSON_MERGE_PATCH,
new V1Patch(channel.client().getJSON().serialize(patch)))
.throwsApiException();
}
var stsDef = Dynamics.newFromYaml(out.toString());
PatchOptions opts = new PatchOptions();
opts.setForce(false);
opts.setFieldManager("kubernetes-java-kubectl-apply");
stsApi.patch(stsDef.getMetadata().getNamespace(),
stsDef.getMetadata().getName(), V1Patch.PATCH_FORMAT_APPLY_YAML,
new V1Patch(channel.client().getJSON().serialize(stsDef)),
opts).throwsApiException();
}
}

View file

@ -34,6 +34,7 @@ import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesApi;
import io.kubernetes.client.util.generic.options.ListOptions;
import java.io.IOException;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -100,6 +101,8 @@ public class VmWatcher extends Component {
}
}
@SuppressWarnings({ "PMD.AvoidInstantiatingObjectsInLoops",
"PMD.CognitiveComplexity" })
private void purge(CustomObjectsApi coa, List<String> vmOpApiVersions)
throws ApiException {
// Get existing CRs (VMs)
@ -117,20 +120,28 @@ public class VmWatcher extends Component {
opts.setLabelSelector(
"app.kubernetes.io/managed-by=vmoperator,"
+ "app.kubernetes.io/name=vmrunner");
for (var version : vmOpApiVersions) {
for (String resource : List.of("pods", "configmaps",
"persistentvolumeclaims", "secrets")) {
// Get resources, selected by label
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
var api
= new DynamicKubernetesApi("", version, resource, client);
for (var obj : api.list(managedNamespace, opts).getObject()
.getItems()) {
String instance = obj.getMetadata().getLabels()
.get("app.kubernetes.io/instance");
if (!known.contains(instance)) {
api.delete(managedNamespace,
obj.getMetadata().getName());
for (String resource : List.of("apps/v1/statefulsets",
"v1/persistentvolumeclaims", "v1/configmaps", "v1/secrets")) {
var resParts = new LinkedList<>(List.of(resource.split("/")));
var group = resParts.size() == 3 ? resParts.poll() : "";
var version = resParts.poll();
var plural = resParts.poll();
// Get resources, selected by label
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
var api = new DynamicKubernetesApi(group, version, plural, client);
var listObj = api.list(managedNamespace, opts).getObject();
if (listObj == null) {
continue;
}
for (var obj : listObj.getItems()) {
String instance = obj.getMetadata().getLabels()
.get("app.kubernetes.io/instance");
if (!known.contains(instance)) {
var resName = obj.getMetadata().getName();
var result = api.delete(managedNamespace, resName);
if (!result.isSuccess()) {
logger.warning(() -> "Cannot cleanup resource \""
+ resName + "\": " + result.toString());
}
}
}