Viewer ACL (#26)
Some checks failed
Java CI with Gradle / build (push) Has been cancelled

Provide ACLs (together with general improvements) for the viewer conlet.
This commit is contained in:
Michael N. Lipp 2024-06-01 11:12:15 +02:00 committed by GitHub
parent a6525a2289
commit 659463b3b4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
42 changed files with 1664 additions and 679 deletions

View file

@ -22,6 +22,7 @@ dependencies {
implementation 'org.jgrapes:org.jgrapes.webconsole.vuejs:[1.5.0,2)'
implementation 'org.jgrapes:org.jgrapes.webconsole.rbac:[1.3.0,2)'
implementation 'org.jgrapes:org.jgrapes.webconlet.oidclogin:[1.3.0,2)'
implementation 'org.jgrapes:org.jgrapes.webconlet.markdowndisplay:[1.2.0,2)'
runtimeOnly 'org.jgrapes:org.jgrapes.webconlet.sysinfo:[1.4.0,2)'
runtimeOnly 'org.jgrapes:org.jgrapes.webconlet.logviewer:[0.2.0,2)'

View file

@ -0,0 +1,5 @@
You can use the "puzzle piece" icon on the top right corner of the
page to add display widgets (conlets) to the overview tab.
Use the "full screen" icon on the top right corner of any
conlet (if available) to get a detailed view.

View file

@ -0,0 +1,6 @@
Verwenden Sie das "Puzzle"-Icon auf der rechten oberen Ecke
der Seite, um Anzeige-Widgets (Conlets) hinzuzufügen.
Wenn sich in der rechten oberen Ecke eines Conlets ein Vollbild-Icon
befindet, können Sie es verwenden, um eine Detailansicht in einem neuen
Register anzufordern.

View file

@ -17,3 +17,4 @@
#
consoleTitle = VM-Operator
introTitle = Usage

View file

@ -0,0 +1,19 @@
#
# VM-Operator
# Copyright (C) 2024 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 <http://www.gnu.org/licenses/>.
#
introTitle = Benutzung

View file

@ -18,11 +18,17 @@
package org.jdrupes.vmoperator.manager;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Collections;
import java.util.ResourceBundle;
import java.util.stream.Collectors;
import org.jgrapes.core.Channel;
import org.jgrapes.core.Component;
import org.jgrapes.core.annotation.Handler;
import org.jgrapes.webconsole.base.Conlet;
import org.jgrapes.webconlet.markdowndisplay.MarkdownDisplayConlet;
import org.jgrapes.webconsole.base.Conlet.RenderMode;
import org.jgrapes.webconsole.base.ConsoleConnection;
import org.jgrapes.webconsole.base.events.AddConletRequest;
import org.jgrapes.webconsole.base.events.ConsoleConfigured;
@ -63,10 +69,13 @@ public class AvoidEmptyPolicy extends Component {
* @param event the event
* @param connection the connection
*/
@Handler
@Handler(priority = 100)
public void onRenderConlet(RenderConlet event,
ConsoleConnection connection) {
connection.session().put(renderedFlagName, true);
if (event.renderAs().contains(RenderMode.Preview)
|| event.renderAs().contains(RenderMode.View)) {
connection.session().put(renderedFlagName, true);
}
}
/**
@ -76,18 +85,42 @@ public class AvoidEmptyPolicy extends Component {
* @param connection the console connection
* @throws InterruptedException the interrupted exception
*/
@Handler
@Handler(priority = -100)
public void onConsoleConfigured(ConsoleConfigured event,
ConsoleConnection connection) throws InterruptedException,
IOException {
if ((Boolean) connection.session().getOrDefault(
renderedFlagName, false)) {
if ((Boolean) connection.session().getOrDefault(renderedFlagName,
false)) {
return;
}
var resourceBundle = ResourceBundle.getBundle(
getClass().getPackage().getName() + ".l10n", connection.locale(),
getClass().getClassLoader(),
ResourceBundle.Control.getNoFallbackControl(
ResourceBundle.Control.FORMAT_DEFAULT));
var locale = resourceBundle.getLocale().toString();
String shortDesc;
try (BufferedReader shortDescReader
= new BufferedReader(new InputStreamReader(
AvoidEmptyPolicy.class.getResourceAsStream(
"ManagerIntro-Preview" + (locale.isEmpty() ? ""
: "_" + locale) + ".md"),
"utf-8"))) {
shortDesc
= shortDescReader.lines().collect(Collectors.joining("\n"));
}
fire(new AddConletRequest(event.event().event().renderSupport(),
"org.jdrupes.vmoperator.vmconlet.VmConlet",
Conlet.RenderMode
.asSet(Conlet.RenderMode.Preview, Conlet.RenderMode.View)),
MarkdownDisplayConlet.class.getName(),
RenderMode.asSet(RenderMode.Preview))
.addProperty(MarkdownDisplayConlet.CONLET_ID,
getClass().getName())
.addProperty(MarkdownDisplayConlet.TITLE,
resourceBundle.getString("consoleTitle"))
.addProperty(MarkdownDisplayConlet.PREVIEW_SOURCE,
shortDesc)
.addProperty(MarkdownDisplayConlet.DELETABLE, true)
.addProperty(MarkdownDisplayConlet.EDITABLE_BY,
Collections.EMPTY_SET),
connection);
}

View file

@ -104,7 +104,9 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
ListOptions listOpts = new ListOptions();
listOpts.setLabelSelector(
"app.kubernetes.io/managed-by=" + VM_OP_NAME + ","
+ "app.kubernetes.io/name=" + APP_NAME);
+ "app.kubernetes.io/name=" + APP_NAME + ","
+ "app.kubernetes.io/instance=" + newCm.getMetadata()
.getLabels().get("app.kubernetes.io/instance"));
// Get pod, selected by label
var podApi = new DynamicKubernetesApi("", "v1", "pods", client);
var pods = podApi

View file

@ -27,6 +27,12 @@ public class Constants extends org.jdrupes.vmoperator.common.Constants {
/** The Constant COMP_DISPLAY_SECRET. */
public static final String COMP_DISPLAY_SECRET = "display-secret";
/** The Constant DATA_DISPLAY_PASSWORD. */
public static final String DATA_DISPLAY_PASSWORD = "display-password";
/** The Constant DATA_PASSWORD_EXPIRY. */
public static final String DATA_PASSWORD_EXPIRY = "password-expiry";
/** The Constant STATE_RUNNING. */
public static final String STATE_RUNNING = "Running";

View file

@ -101,7 +101,7 @@ public class Controller extends Component {
}
});
attach(new VmMonitor(channel()).channelManager(chanMgr));
attach(new DisplayPasswordMonitor(channel())
attach(new DisplaySecretMonitor(channel())
.channelManager(chanMgr.fixed()));
// Currently, we don't use the IP assigned by the load balancer
// to access the VM's console. Might change in the future.

View file

@ -1,102 +0,0 @@
/*
* VM-Operator
* Copyright (C) 2024 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 io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.models.V1Secret;
import io.kubernetes.client.openapi.models.V1SecretList;
import io.kubernetes.client.util.Watch.Response;
import io.kubernetes.client.util.generic.options.ListOptions;
import java.io.IOException;
import static org.jdrupes.vmoperator.common.Constants.APP_NAME;
import org.jdrupes.vmoperator.common.K8sClient;
import org.jdrupes.vmoperator.common.K8sObserver.ResponseType;
import org.jdrupes.vmoperator.common.K8sV1SecretStub;
import static org.jdrupes.vmoperator.manager.Constants.COMP_DISPLAY_SECRET;
import org.jdrupes.vmoperator.manager.events.DisplayPasswordChanged;
import org.jdrupes.vmoperator.manager.events.GetDisplayPassword;
import org.jdrupes.vmoperator.manager.events.VmChannel;
import org.jgrapes.core.Channel;
import org.jgrapes.core.annotation.Handler;
/**
* Watches for changes of display secrets.
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
public class DisplayPasswordMonitor
extends AbstractMonitor<V1Secret, V1SecretList, VmChannel> {
/**
* Instantiates a new display secrets monitor.
*
* @param componentChannel the component channel
*/
public DisplayPasswordMonitor(Channel componentChannel) {
super(componentChannel, V1Secret.class, V1SecretList.class);
context(K8sV1SecretStub.CONTEXT);
ListOptions options = new ListOptions();
options.setLabelSelector("app.kubernetes.io/name=" + APP_NAME + ","
+ "app.kubernetes.io/component=" + COMP_DISPLAY_SECRET);
options(options);
}
@Override
protected void prepareMonitoring() throws IOException, ApiException {
client(new K8sClient());
}
@Override
protected void handleChange(K8sClient client, Response<V1Secret> change) {
String vmName = change.object.getMetadata().getLabels()
.get("app.kubernetes.io/instance");
if (vmName == null) {
return;
}
var channel = channel(vmName).orElse(null);
if (channel == null || channel.vmDefinition() == null) {
return;
}
channel.pipeline().fire(new DisplayPasswordChanged(
ResponseType.valueOf(change.type), change.object), channel);
}
/**
* On get display secrets.
*
* @param event the event
* @param channel the channel
* @throws ApiException the api exception
*/
@Handler
@SuppressWarnings("PMD.StringInstantiation")
public void onGetDisplaySecrets(GetDisplayPassword event, VmChannel channel)
throws ApiException {
ListOptions options = new ListOptions();
options.setLabelSelector("app.kubernetes.io/name=" + APP_NAME + ","
+ "app.kubernetes.io/component=" + COMP_DISPLAY_SECRET + ","
+ "app.kubernetes.io/instance=" + event.vmName());
var stubs = K8sV1SecretStub.list(client(), namespace(), options);
if (stubs.isEmpty()) {
return;
}
stubs.iterator().next().model().map(m -> m.getData())
.map(m -> m.get("display-password"))
.ifPresent(p -> event.setResult(new String(p)));
}
}

View file

@ -0,0 +1,285 @@
/*
* VM-Operator
* Copyright (C) 2024 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 io.kubernetes.client.custom.V1Patch;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.models.V1Secret;
import io.kubernetes.client.openapi.models.V1SecretList;
import io.kubernetes.client.util.Watch.Response;
import io.kubernetes.client.util.generic.options.ListOptions;
import io.kubernetes.client.util.generic.options.PatchOptions;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.time.Instant;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.logging.Level;
import static org.jdrupes.vmoperator.common.Constants.APP_NAME;
import static org.jdrupes.vmoperator.common.Constants.VM_OP_NAME;
import org.jdrupes.vmoperator.common.K8sClient;
import org.jdrupes.vmoperator.common.K8sV1PodStub;
import org.jdrupes.vmoperator.common.K8sV1SecretStub;
import static org.jdrupes.vmoperator.manager.Constants.COMP_DISPLAY_SECRET;
import static org.jdrupes.vmoperator.manager.Constants.DATA_DISPLAY_PASSWORD;
import static org.jdrupes.vmoperator.manager.Constants.DATA_PASSWORD_EXPIRY;
import org.jdrupes.vmoperator.manager.events.GetDisplayPassword;
import org.jdrupes.vmoperator.manager.events.VmChannel;
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
import org.jgrapes.core.Channel;
import org.jgrapes.core.CompletionLock;
import org.jgrapes.core.Event;
import org.jgrapes.core.annotation.Handler;
import org.jgrapes.util.events.ConfigurationUpdate;
import org.jose4j.base64url.Base64;
/**
* Watches for changes of display secrets.
*/
@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.TooManyStaticImports" })
public class DisplaySecretMonitor
extends AbstractMonitor<V1Secret, V1SecretList, VmChannel> {
private int passwordValidity = 10;
private final List<PendingGet> pendingGets
= Collections.synchronizedList(new LinkedList<>());
/**
* Instantiates a new display secrets monitor.
*
* @param componentChannel the component channel
*/
public DisplaySecretMonitor(Channel componentChannel) {
super(componentChannel, V1Secret.class, V1SecretList.class);
context(K8sV1SecretStub.CONTEXT);
ListOptions options = new ListOptions();
options.setLabelSelector("app.kubernetes.io/name=" + APP_NAME + ","
+ "app.kubernetes.io/component=" + COMP_DISPLAY_SECRET);
options(options);
}
/**
* On configuration update.
*
* @param event the event
*/
@Handler
@Override
public void onConfigurationUpdate(ConfigurationUpdate event) {
super.onConfigurationUpdate(event);
event.structured(componentPath()).ifPresent(c -> {
try {
if (c.containsKey("passwordValidity")) {
passwordValidity = Integer
.parseInt((String) c.get("passwordValidity"));
}
} catch (ClassCastException e) {
logger.config("Malformed configuration: " + e.getMessage());
}
});
}
@Override
protected void prepareMonitoring() throws IOException, ApiException {
client(new K8sClient());
}
@Override
protected void handleChange(K8sClient client, Response<V1Secret> change) {
String vmName = change.object.getMetadata().getLabels()
.get("app.kubernetes.io/instance");
if (vmName == null) {
return;
}
var channel = channel(vmName).orElse(null);
if (channel == null || channel.vmDefinition() == null) {
return;
}
try {
patchPod(client, change);
} catch (ApiException e) {
logger.log(Level.WARNING, e,
() -> "Cannot patch pod annotations: " + e.getMessage());
}
}
private void patchPod(K8sClient client, Response<V1Secret> change)
throws ApiException {
// Force update for pod
ListOptions listOpts = new ListOptions();
listOpts.setLabelSelector(
"app.kubernetes.io/managed-by=" + VM_OP_NAME + ","
+ "app.kubernetes.io/name=" + APP_NAME + ","
+ "app.kubernetes.io/instance=" + change.object.getMetadata()
.getLabels().get("app.kubernetes.io/instance"));
// Get pod, selected by label
var pods = K8sV1PodStub.list(client, namespace(), listOpts);
// If the VM is being created, the pod may not exist yet.
if (pods.isEmpty()) {
return;
}
var pod = pods.iterator().next();
// Patch pod annotation
PatchOptions patchOpts = new PatchOptions();
patchOpts.setFieldManager("kubernetes-java-kubectl-apply");
pod.patch(V1Patch.PATCH_FORMAT_JSON_PATCH,
new V1Patch("[{\"op\": \"replace\", \"path\": "
+ "\"/metadata/annotations/vmrunner.jdrupes.org~1dpVersion\", "
+ "\"value\": \""
+ change.object.getMetadata().getResourceVersion()
+ "\"}]"),
patchOpts);
}
/**
* On get display secrets.
*
* @param event the event
* @param channel the channel
* @throws ApiException the api exception
*/
@Handler
@SuppressWarnings("PMD.StringInstantiation")
public void onGetDisplaySecrets(GetDisplayPassword event, VmChannel channel)
throws ApiException {
ListOptions options = new ListOptions();
options.setLabelSelector("app.kubernetes.io/name=" + APP_NAME + ","
+ "app.kubernetes.io/component=" + COMP_DISPLAY_SECRET + ","
+ "app.kubernetes.io/instance="
+ event.vmDefinition().metadata().getName());
var stubs = K8sV1SecretStub.list(client(),
event.vmDefinition().metadata().getNamespace(), options);
if (stubs.isEmpty()) {
return;
}
var stub = stubs.iterator().next();
// Check validity
var model = stub.model().get();
@SuppressWarnings("PMD.StringInstantiation")
var expiry = new String(model.getData().get(DATA_PASSWORD_EXPIRY));
if (model.getData().get(DATA_DISPLAY_PASSWORD) != null
&& stillValid(expiry)) {
event.setResult(
new String(model.getData().get(DATA_DISPLAY_PASSWORD)));
return;
}
updatePassword(stub, event);
}
@SuppressWarnings("PMD.StringInstantiation")
private void updatePassword(K8sV1SecretStub stub, GetDisplayPassword event)
throws ApiException {
SecureRandom random = null;
try {
random = SecureRandom.getInstanceStrong();
} catch (NoSuchAlgorithmException e) { // NOPMD
// "Every implementation of the Java platform is required
// to support at least one strong SecureRandom implementation."
}
byte[] bytes = new byte[16];
random.nextBytes(bytes);
var password = Base64.encode(bytes);
var model = stub.model().get();
model.setStringData(Map.of(DATA_DISPLAY_PASSWORD, password,
DATA_PASSWORD_EXPIRY,
Long.toString(Instant.now().getEpochSecond() + passwordValidity)));
event.setResult(password);
// Prepare wait for confirmation (by VM status change)
var pending = new PendingGet(event,
event.vmDefinition().displayPasswordSerial().orElse(0L) + 1,
new CompletionLock(event, 1500));
pendingGets.add(pending);
Event.onCompletion(event, e -> {
pendingGets.remove(pending);
});
// Update, will (eventually) trigger confirmation
stub.update(model).getObject();
}
private boolean stillValid(String expiry) {
if (expiry == null || "never".equals(expiry)) {
return true;
}
@SuppressWarnings({ "PMD.CloseResource", "resource" })
var scanner = new Scanner(expiry);
if (!scanner.hasNextLong()) {
return false;
}
long expTime = scanner.nextLong();
return expTime > Instant.now().getEpochSecond() + passwordValidity;
}
/**
* On vm def changed.
*
* @param event the event
* @param channel the channel
*/
@Handler
public void onVmDefChanged(VmDefChanged event, Channel channel) {
synchronized (pendingGets) {
String vmName = event.vmDefinition().metadata().getName();
for (var pending : pendingGets) {
if (pending.event.vmDefinition().metadata().getName()
.equals(vmName)
&& event.vmDefinition().displayPasswordSerial()
.map(s -> s >= pending.expectedSerial).orElse(false)) {
pending.lock.remove();
// pending will be removed from pendingGest by
// waiting thread, see updatePassword
continue;
}
}
}
}
/**
* The Class PendingGet.
*/
@SuppressWarnings("PMD.DataClass")
private static class PendingGet {
public final GetDisplayPassword event;
public final long expectedSerial;
public final CompletionLock lock;
/**
* Instantiates a new pending get.
*
* @param event the event
* @param expectedSerial the expected serial
*/
public PendingGet(GetDisplayPassword event, long expectedSerial,
CompletionLock lock) {
super();
this.event = event;
this.expectedSerial = expectedSerial;
this.lock = lock;
}
}
}

View file

@ -0,0 +1,106 @@
/*
* 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.JsonPrimitive;
import freemarker.template.TemplateException;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.models.V1ObjectMeta;
import io.kubernetes.client.openapi.models.V1Secret;
import io.kubernetes.client.util.generic.options.ListOptions;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Map;
import java.util.logging.Logger;
import org.jdrupes.vmoperator.common.K8sV1SecretStub;
import static org.jdrupes.vmoperator.manager.Constants.APP_NAME;
import static org.jdrupes.vmoperator.manager.Constants.COMP_DISPLAY_SECRET;
import static org.jdrupes.vmoperator.manager.Constants.DATA_DISPLAY_PASSWORD;
import static org.jdrupes.vmoperator.manager.Constants.DATA_PASSWORD_EXPIRY;
import org.jdrupes.vmoperator.manager.events.VmChannel;
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
import org.jdrupes.vmoperator.util.GsonPtr;
import org.jose4j.base64url.Base64;
/**
* Delegee for reconciling the display secret
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
/* default */ class DisplaySecretReconciler {
protected final Logger logger = Logger.getLogger(getClass().getName());
/**
* Reconcile.
*
* @param event the event
* @param model the model
* @param channel the channel
* @throws IOException Signals that an I/O exception has occurred.
* @throws TemplateException the template exception
* @throws ApiException the api exception
*/
public void reconcile(VmDefChanged event,
Map<String, Object> model, VmChannel channel)
throws IOException, TemplateException, ApiException {
// Secret needed at all?
var display = GsonPtr.to(event.vmDefinition().data()).to("spec", "vm",
"display");
if (!display.get(JsonPrimitive.class, "spice", "generateSecret")
.map(JsonPrimitive::getAsBoolean).orElse(false)) {
return;
}
// Check if exists
var metadata = event.vmDefinition().getMetadata();
ListOptions options = new ListOptions();
options.setLabelSelector("app.kubernetes.io/name=" + APP_NAME + ","
+ "app.kubernetes.io/component=" + COMP_DISPLAY_SECRET + ","
+ "app.kubernetes.io/instance=" + metadata.getName());
var stubs = K8sV1SecretStub.list(channel.client(),
metadata.getNamespace(), options);
if (!stubs.isEmpty()) {
return;
}
// Create secret
var secret = new V1Secret();
secret.setMetadata(new V1ObjectMeta().namespace(metadata.getNamespace())
.name(metadata.getName() + "-" + COMP_DISPLAY_SECRET)
.putLabelsItem("app.kubernetes.io/name", APP_NAME)
.putLabelsItem("app.kubernetes.io/component", COMP_DISPLAY_SECRET)
.putLabelsItem("app.kubernetes.io/instance", metadata.getName()));
secret.setType("Opaque");
SecureRandom random = null;
try {
random = SecureRandom.getInstanceStrong();
} catch (NoSuchAlgorithmException e) { // NOPMD
// "Every implementation of the Java platform is required
// to support at least one strong SecureRandom implementation."
}
byte[] bytes = new byte[16];
random.nextBytes(bytes);
var password = Base64.encode(bytes);
secret.setStringData(Map.of(DATA_DISPLAY_PASSWORD, password,
DATA_PASSWORD_EXPIRY, "now"));
K8sV1SecretStub.create(channel.client(), secret);
}
}

View file

@ -135,6 +135,7 @@ public class Reconciler extends Component {
@SuppressWarnings("PMD.SingularField")
private final Configuration fmConfig;
private final ConfigMapReconciler cmReconciler;
private final DisplaySecretReconciler dsReconciler;
private final StatefulSetReconciler stsReconciler;
private final LoadBalancerReconciler lbReconciler;
@SuppressWarnings("PMD.UseConcurrentHashMap")
@ -159,6 +160,7 @@ public class Reconciler extends Component {
fmConfig.setClassForTemplateLoading(Reconciler.class, "");
cmReconciler = new ConfigMapReconciler(fmConfig);
dsReconciler = new DisplaySecretReconciler();
stsReconciler = new StatefulSetReconciler(fmConfig);
lbReconciler = new LoadBalancerReconciler(fmConfig);
}
@ -209,6 +211,7 @@ public class Reconciler extends Component {
= prepareModel(channel.client(), patchCr(event.vmDefinition()));
var configMap = cmReconciler.reconcile(event, model, channel);
model.put("cm", configMap.getRaw());
dsReconciler.reconcile(event, model, channel);
stsReconciler.reconcile(event, model, channel);
lbReconciler.reconcile(event, model, channel);
}

View file

@ -32,12 +32,14 @@ import static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP;
import org.jdrupes.vmoperator.common.K8s;
import org.jdrupes.vmoperator.common.K8sClient;
import org.jdrupes.vmoperator.common.K8sDynamicModel;
import org.jdrupes.vmoperator.common.K8sDynamicModels;
import org.jdrupes.vmoperator.common.K8sDynamicStub;
import org.jdrupes.vmoperator.common.K8sObserver.ResponseType;
import org.jdrupes.vmoperator.common.K8sV1ConfigMapStub;
import org.jdrupes.vmoperator.common.K8sV1PodStub;
import org.jdrupes.vmoperator.common.K8sV1StatefulSetStub;
import org.jdrupes.vmoperator.common.VmDefinitionModel;
import org.jdrupes.vmoperator.common.VmDefinitionModels;
import org.jdrupes.vmoperator.common.VmDefinitionStub;
import static org.jdrupes.vmoperator.manager.Constants.APP_NAME;
import static org.jdrupes.vmoperator.manager.Constants.VM_OP_KIND_VM;
import static org.jdrupes.vmoperator.manager.Constants.VM_OP_NAME;
@ -50,8 +52,8 @@ import org.jgrapes.core.Channel;
* Watches for changes of VM definitions.
*/
@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.ExcessiveImports" })
public class VmMonitor
extends AbstractMonitor<K8sDynamicModel, K8sDynamicModels, VmChannel> {
public class VmMonitor extends
AbstractMonitor<VmDefinitionModel, VmDefinitionModels, VmChannel> {
/**
* Instantiates a new VM definition watcher.
@ -59,7 +61,8 @@ public class VmMonitor
* @param componentChannel the component channel
*/
public VmMonitor(Channel componentChannel) {
super(componentChannel, K8sDynamicModel.class, K8sDynamicModels.class);
super(componentChannel, VmDefinitionModel.class,
VmDefinitionModels.class);
}
@Override
@ -102,7 +105,7 @@ public class VmMonitor
@Override
protected void handleChange(K8sClient client,
Watch.Response<K8sDynamicModel> response) {
Watch.Response<VmDefinitionModel> response) {
V1ObjectMeta metadata = response.object.getMetadata();
VmChannel channel = channel(metadata.getName()).orElse(null);
if (channel == null) {
@ -138,9 +141,10 @@ public class VmMonitor
vmDef), channel);
}
private K8sDynamicModel getModel(K8sClient client, K8sDynamicModel vmDef) {
private VmDefinitionModel getModel(K8sClient client,
VmDefinitionModel vmDef) {
try {
return K8sDynamicStub.get(client, context(), namespace(),
return VmDefinitionStub.get(client, context(), namespace(),
vmDef.metadata().getName()).model().orElse(null);
} catch (ApiException e) {
return null;